在学习c语言提高总结了笔记,并分享出来

02-c提高04day

目录:
一、多维数组
1、一维数组
(1)数组名
(2)下标引用
(3)数组和指针
(4)作为函数参数的数组名
2、多维数组
(1)数组名
(2)指向数组的指针(数组指针)
3、总结
(1)编程提示
(2)内容总结
二、结构体
1、结构体基础知识
(1)结构体类型的定义
(2)结构体变量的定义
(3)结构体变量的初始化
(4)结构体成员的使用
2、结构体赋值
(1)赋值基本概念
(2)结构体嵌套一级指针

 

一、多维数组

1、一维数组
·元素类型角度:数组是相同类型的变量的有序集合
·内存角度:连续的一大片内存空间

在讨论多维数组之前,我们还需要学习很多关于一维数组的知识。首先让我们学习一个概念。

(1)数组名
考虑下面这些声明:



1 int a; 2 int b[10];



我们把a称作变量,因为它是个单一的值,这个变量的类型是一个整数。我们把b称作数组,因为它是一些值的集合。下标和数名一起使用,用于标识该集合中某个特定的值。例如,b[0]表示数组b的第1个值,b[4]表示第5个值。每个值都是一个特定的标量。

那么问题是b的类型是什么?它所表示的又是什么?一个合乎逻辑的答案是它表示整个数组,但事实并非如此。在C中,在几乎所有数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址。它的类型取决于数组元素的类型:如果他们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型也就是“指向其他类型的常量指针"。

请问:指针和数组是等价的吗?
答案是否定的。数组名在表达式中使用的时候,编译器才会产生一个指针常量。那么数组在什么情况下不能作为指针常量呢?在以下两种场景下:

■当数组名作为sizeof操作符的操作数的时候,此时sizeof返回的是整个数组的长度,而不是指针数组指针的长度。
■当数组名作为&操作符的操作数的时候,此时返回的是一个指向数组的指针,而不是指向某个数组元素的指针常量。



1 int arr[10];
2 //arr=NULL;//arr作为指针常量,不可修改
3 int *p=arr;//此时arr作为指针常量来使用
4 printf(""sizeof(arr):%d\n",sizeof(arr));//此时sizeof结果为整个数组的长度
5 printf("&arr type is %s\n",typeid(&arr).name());//int(*)[10]而不是int*



 练习:



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 void test()
7 {
8 int arr[] = {1, 2, 3, 4};
9
10 //1.sizeof 2.对数组名取地址&arr
11 //以上两种情况下,数组名不是指向首元素的指针
12 //以上两种情况下,数组名是数组类型
13 //除了以上两点之外,数组名在其他任何情况下都是指向首元素的指针
14
15 printf("sizeof arr:%d\n", sizeof(arr);
16
17 printf("&arr addr: %d\n", &arr);
18 printf("&arr + 1 addr:%d\n", &arr + 1);
19
20 //arr = NULL;//err,数组名是一个常量指针
21
22 }
23
24
25 int main(){
26
27 test();
28
29 system("pause");
30 return EXIT_SUCCESS;
31 }



(2)下标引用
int arr[]={1,2,3,4,5,6};

*(arr+3),这个表达式是什么意思呢?
首先,我们说数组在表达式中是一个指向整型的指针,所以此表达式表示arr 指针向后移动了3个元素的长度。然后通过间接访问操作符从这个新地址开始获取这个位置的值。这个和下标的引用的执行过程完全相同。所以如下表达式是等同的:



1 *(arr + 3) 2 arr[3]



问题1:数组下标可否为负值?

问题2:请阅读如下代码,说出结果:



1 int arr[]={5,3,6,8,2,9};
2 int *p=arr +2;
3 printf("*p=%d\n",*p);
4 printf("*p=%d\n",p[-1]);



那么是用下标还是指针来操作数组呢?对于大部分人而言,下标的可读性会强一些。



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 //代码可读性,可读性比效率更重要
7 void printArray(int* arr, int len)//int* arr最好写成int arr[]
8 {
9 for(int i = 0; i < len; ++i)
10 {
11 printf("%d", arr[i]);//这种可读性好
12 printf("%d", *(arr + i));
13 }
14 }
15
16 void test()
17 {
18 int arr[] = {1, 2, 3, 4};
19
20 //1.sizeof 2.对数组名取地址&arr
21 //以上两种情况下,数组名不是指向首元素的指针
22 //以上两种情况下,数组名是数组类型
23 //除了以上两点之外,数组名在其他任何情况下都是指向首元素的指针
24
25 printf("sizeof arr:%d\n", sizeof(arr);
26
27 printf("&arr addr: %d\n", &arr);
28 printf("&arr + 1 addr:%d\n", &arr + 1);
29
30 //arr = NULL;//err,数组名是一个常量指针
31
32 //数组下标能否是负数?
33 int* p = arr;
34 p += 3;
35 printf("p[-1]:%d", p[-1]);//p[-1]转换为*(p-1),所以可以为负数
36 }
37
38
39 int main(){
40
41 test();
42
43 system("pause");
44 return EXIT_SUCCESS;
45 }



(3)数组和指针
指针和数组并不是相等的。为了说明这个概念,请考虑下面两个声明:



1 int a[10]; 2 int *b;



声明一个数组时,编译器根据声明所指定的元素数量为数组分配内存空间,然后再创建数组名,指向这段空间的起始位置。声明一个指针变量的时候,编译器只为指针本身分配内存空间,并不为任何整型值分配内存空间,指针并未初始化指向任何现有的内存空间。

因此,表达式*a是完全合法的,但是表达式*b却是非法的。*b将访问内存中一个不确定的位置,将会导致程序终止。另一方面b++可以通过编译,a++却不行,因为a是一个常量值。

 

(4)作为函数参数的数组名
当一个数组名作为一个参数传递给一个函数的时候发生什么情况呢?我们现在知道数组名其实就是一个指向数组第1个元素的指针,所以很明白此时传递给函数的是一份指针的拷贝。所以函数的形参实际上是一个指针。但是为了使程序员新手容易上手一些,编译器也接受数组形式的函数形参。因此下面两种函数原型是相等的:

int print array(int *arr);
int print array(int arr[]);

我们可以使用任何一种声明,但哪一个更准确一些呢?答案是指针。因为实参实际上是个指针,而不是数组。同样 sizeof arr值是指针的长度,而不是数组的长度。

现在我们清楚了,为什么一维数组中无须写明它的元素数目了因为形参只是一个指针,并不需要为数组参数分配内存。另一方面,这种方式使得函数无法知道数组的长度。如果函数需要知道数组的长度,它必须显式传递一个长度参数给函数。

2、多维数组

如果某个数组的维数不止1个,它就被称为多维数组。

(1)数组名
一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”,它指向数组的第1个元素。多维数组也是同理,多维数组的数组名也是指向第一个元素,只不过第一个元素是一个数组。例如:



1 int arr[3][10]



可以理解为这是一个一维数组,包含了3个元素,只是每个元素恰好是包含了10个元素的数组。arr就表示指向它的第1个元素的指针,所以arr是一个指向了包含了10个整型元素的数组的指针。

(2)指向数组的指针(数组指针)
数组指针,它是指针,指向数组的指针。
数组的类型由元素类型和数组大小共同决定:int array[5]的类型为int[5];C语言可通过typedef定义一个数组类型:

定义数组指针有一下三种方式:



1 //方式一
2 void test01(){
3 //先定义数组类型,再用数组类型定义数组指针
4 int arr[10]={1,2,3,4,5,6,7,8,9,10};
5
6 //有typedef是定义类型,没有则是定义变量,下面代码定义了一个数组类型ArrayType
7 typedef int(ArrayType)[10];
8 //int ArrayType[10];/定义一个数组,数组名为ArrayType
9
10 ArrayType myarr;//等价于int myarr[10];
11 ArrayType* pArr=&arr;//定义了一个数组指针pArr,并且指针指向数组arr
12 for(int i=0;i<10;i++){
13 printf("%d",(*pArr)[i]);
14 }
15 printf("\n");
16 }
17 //方式二
18 void test02(){
19 int arr[10];
20 //定义数组指针类型
21 typedef int(*ArrayType)[10];
22 ArrayType pArr=&arr;//定义了一个数组指针pArr,并且指针指向数组arr
23 for(int i=0;i<10;i++){
24 (*pArr)[i]=i+1;
25 }
26 for(int i=0;i<10;i++){
27 printf("%d",(*pArr)[i]);
28 }
29 printf("\n");
30 }
31 //方式三
32 void test03(){
33 int arr[10];
34 int(*pArr)[10]=&arr;
35 for(int i=0;i<10;i++){
36 (*pArr)[i]=i+1;
37 }
38 for(int i=0;i<10;i++){
39 printf("%d",(*pArr)[i]);
40 }
41 printf("\n");
42 }



练习1:数组指针


1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h> 5 6 //如何定义一个可以指向数组的指针? 7 void test() 8 { 9 int arr[5] = {1, 2, 3, 4, 5}; 10 11 //1.我们先定义数组类型,再定义数组指针类型 12 typedef int (ARRAY_TYPE)[5]; 13 14 ARRAY_TYPE myarray;//相当于int myarray[5] 15 for(int i = 0; i < 5; ++i) 16 { 17 myarray[i] = 100 + i; 18 } 19 for(int i = 0; i < 5; ++i) 20 { 21 printf("%d ", myarray[i]); 22 } 23 24 //对数组名取地址代表指向整个数组的指针 25 ARRAY_TYPE* pArray = &myarray; 26 pArray = &arr; 27 28 //1)*pArray表示拿到pArray指针指向的整个数组 29 //2)*pArray类型就是数组名,指向首元素类型的指针 30 //*pArray先解引用,拿到数组名,即拿到指向首元素的指针,+1,拿到指向第二个元素的指针,再解引用,拿到第二个元素 31 printf("*(*pArray + 1):%d\n", *(*pArray + 1));//输出2 32 33 //2.直接定义数组指针类型 34 typedef int(*ARRAY_POINTER)[5]; 35 ARRAY_POINTER pArr = &arr; 36 37 //3.直接定义数组指针变量 38 int(*pArrParam)[5] = &arr; 39 40 } 41 42 43 int main(){ 44 45 test(); 46 47 system("pause"); 48 return EXIT_SUCCESS; 49 }



练习2:通过指针操作二维数组



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 void test()
7 {
8 int arr[3][3] =
9 {
10 {1,2,3},
11 {4,5,6},
12 {7,8,9}
13 };//可读性更强,相当于int arr[3][3] = {1,2,3,4,5,6,7,8,9};
14
15 //对于二维数组同一维数组一样,除了sizeof对数组名取地址之外,那么数组名就是指向数组首元素的指针
16 printf("*(*(arr + 2) + 1):%d", *(*(arr + 2) + 1));
17 }
18
19
20 int main(){
21
22 test();
23
24 system("pause");
25 return EXIT_SUCCESS;
26 }



 练习3:二维数组当做函数形参



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 void printBiArray(int(*parr)[3], int len1, int len2)
7 {
8 for(int i = 0; i < len1; ++i)
9 {
10 for(j = 0; j < len2; ++j)
11 {
12 //printf("%d ", *(*(parr + i) + j));
13 printf("%d ", parr[i][j]);//可读性强
14 }
15 }
16 }
17
18 void test()
19 {
20 int arr[3][3] =
21 {
22 {1,2,3},
23 {4,5,6},
24 {7,8,9}
25 };//可读性更强,相当于int arr[3][3] = {1,2,3,4,5,6,7,8,9};
26 printBiArray(arr, 3, 3);
27
28 }
29
30
31 int main(){
32
33 test();
34
35 system("pause");
36 return EXIT_SUCCESS;
37 }



 练习4:指针数组排序——选择排序



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 void SelectSort(char** arr, int len)
7 {
8 for(int i = 0; i < len; ++i)
9 {
10 int min = i;
11 for(int j = i + 1; j < len; ++j)
12 {
13 if(strcmp(arr[j], arr[min]) < 0)
14 {
15 min = j;
16 }
17 }
18 //交换
19 if(min != i)
20 {
21 char* temp = arr[min];
22 arr[min] = arr[i];
23 arr[i] = temp;
24 }
25 }
26 }
27
28 void PrintArray(char** arr, int len)
29 {
30 for(int i = 0; i < len; ++i)
31 {
32 printf("%s\n", arr[i]);
33 }
34 }
35
36 void test()
37 {
38 char* pArr[] = {"ddd", "ccc", "fff", "hhh", "ppp", "rrr"};
39 //pArr是什么类型的?char**类型的
40 int len = sizeof(pArr) / sizeof(char*);//等价于sizeof(pArr) / sizeof(pArr[0])
41 PrintArray(pArr, len);
42 //选择排序
43 SelectSort(pArr, len);
44 printf("-----------------------\n");
45 PrintArray(pArr, len);
46 }
47
48 int main(){
49
50 test();
51
52 system("pause");
53 return EXIT_SUCCESS;
54 }



3、总结

(1)编程提示
■源代码的可读性几乎总是比程序的运行时效率更为重要
■只要有可能,函数的指针形参都应该声明为const
■在多维数组的初始值列表中使用完整的多层花括号提供可读性

(2)内容总结
在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。这个规则只有两个例外,sizeof和对数组名&。

指针和数组并不相等。当我们声明一个数组的时候,同时也分配了内存。但是声明指针的时候,只分配容纳指针本身的空间。

当数组名作为函数参数时,实际传递给函数的是一个指向数组第1个元素的指针
我们不单可以创建指向普通变量的指针,也可创建指向数组的指针。

二、结构体

1、结构体基础知识
(1)结构体类型的定义



1 struct Person{
2 char name[64];
3 int age;
4 };
 typedef struct Person MyPerson;
//等价于下边
5 typedef struct Person{
6 char name[64];
7 int age;
8 }MyPerson;



注意:定义结构体类型时不要直接给成员赋值,结构体只是一个类型,编译器还没有为其分配空间,只有根据其类型定义变量时,才分配空间,有空间后才能赋值。

(2)结构体变量的定义


1 struct Person{
2 char name[64];
3 int age;
4 }p1;//定义类型同时定义变量
5
6 struct{
7 char name[64];
8 int age;
9 }p2;//定义类型同时定义变量
10
11 struct Person p3;//通过类型直接定义



(3)结构体变量的初始化



1 struct Person{
2 char name[64];
3 int age;
4 }pl={"john",10};//定义类型同时初始化变量
5
6 struct{
7 char name[64];
8 int age;
9 }p2={"Obama",30};//定义类型同时初始化变量
10
11 struct Person p3={"Edward",33};//通过类型直接定义



(4)结构体成员的使用

单个结构体变量



1 struct Person{
2 char name[64];
3 int age;
4 };
5 void test(){
6 //在栈上分配空间
7 struct Person pl;
8 strcpy(pl.name,"John");
9 pl.age=30;
10 //如果是普通变量,通过点运算符操作结构体成员
11 printf("Name:%s Age:%d\n",pl.name,pl.age);
12
13 //在堆上分配空间
14 struct Person*p2=(struct Person*)malloc(sizeof(struct Person));
15 strcpy(p2->name,"Obama");
16 p2->age=33;
17 //如果是指针变量,通过->操作结构体成员
18 printf("Name:%s Age:%d\n",p2->name,p2->age);
19 }



多个结构体变量



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 struct Person{
7 char name[64];
8 int age;
9 };
10
11 //打印
12 void printPersons(struct* persons, int len)
13 {
14 for(int i = 0; i < len; ++i)
15 {
16 printf("Name:%s Age:%d\n", persons[i].name, persons[i].Age);
17 }
18 }
19
20 //多个结构体变量
21 void test()
22 {
23 //在栈上分配结构体数组空间
24 struct Person persons[] = {
25 {"aaa", 20},
26 {"bbb", 30},
27 {"ccc", 40},
28 {"ddd", 50},
29 {"eee", 60},
30 {"fff", 70},
31 };
32 int len = sizeof(persons) / sizeof(struct Person);//或者用sizeof(struct persons[0])
33 printPersons(persons, len);
34
35 //在堆上分配结构体数组空间
36 struct Person* ps = malloc(sizeof(struct Person) * 6);
37 for(int i = 0; i < 6; ++i)
38 {
39 sprintf(ps[i].name, "Name_%d", i + 1);
40 ps[i].age = 100 + i;
41 }
42 printPersons(ps, 6);
43 }
44
45 int main(){
46
47 test();
48
49 system("pause");
50 return EXIT_SUCCESS;
51 }


 

2、结构体赋值
(1)赋值基本概念
相同的两个结构体变量可以相互赋值,把一个结构体变量的值拷贝给另一个结构体(逐字节拷贝),这两个变量还是两个独立的变量。



1 struct Person{
2 char name[64];
3 int age;
4 };
5 void test(){
6 //在栈上分配空间
7 struct Person pl={"John",30};
8 struct Person p2={"Obama",33};
9 printf("Name:%s Age:%d\n",pl.name,pl.age);
10 printf("Name:%s Age:%d\n",p2.name,p2.age);
11 //将p2的值赋值给p1
12 p1=p2;
13 printf("Name:%s Age:%d\n",pl.name,pl.age);
14 printf("Name:%s Age:%d\n",p2.name,p2.age);
15 }



练习:堆上结构体的拷贝



1 #define _CRT_SECURE_NO_WARNINGS
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 struct Teacher{
7 char* name;
8 int age;
9 };
10
11
12 void test()
13 {
14 struct Teacher teacher1;
15 teacher1.name = malloc(sizeof(char) * 64);//在堆上开辟内存
16 memset(teacher1.name, 0, 64);
17 strcpy(teacher1.name, "aaa");
18 teacher1.age = 20;
19
20 struct Teacher teacher2;
21 teacher2.name = malloc(sizeof(char) * 128);
22 memset(teacher2.name, 0, 128);
23 strcpy(teacher2.name, "bbbbbbbbbbbbb");
24 teacher2.age = 30;
25
26 printf("Name:%s Age:%d\n",teacher1.name,teacher1.age);
27 printf("Name:%s Age:%d\n",teacher2.name,teacher2.age);
28
29 //赋值报错?分析???
30 //不使用默认的结构体
31 //teacher1 = teacher2;//err
32 //如果结构体内部有指针指向堆内存,那么就不能使用编译器默认的赋值行为,应该手动控制赋值过程
33 //手动拷贝
34 if(teacher1.name != NULL)
35 {
36 free(teacher1.name);
37 teacher1.name = NULL;
38 }
39 teacher1.name = malloc(strlen(teacher2.name) + 1);
40 strcpy(teacher1.name, teacher2.name);
41 teacher1.age = teacher2.age;
42
43 printf("------------------\n");
44 printf("Name:%s Age:%d\n",teacher1.name,teacher1.age);
45 printf("Name:%s Age:%d\n",teacher2.name,teacher2.age);
46
47
48 //释放堆内存
49 if(teacher1.name != NULL)
50 {
51 free(teacher1.name);
52 teacher1.name = NULL;
53 }
54 if(teacher2.name != NULL)
55 {
56 free(teacher2.name);
57 teacher2.name = NULL;
58 }
59 }
60
61 int main(){
62
63 test();
64
65 system("pause");
66 return EXIT_SUCCESS;
67 }



 赋值报错?分析?

c语言提高学习笔记——02-c提高04day_数组名


c语言提高学习笔记——02-c提高04day_数组名_02

解决:

c语言提高学习笔记——02-c提高04day_数组名_03

(2)结构体嵌套一级指针

练习:

c语言提高学习笔记——02-c提高04day_#include_04

代码实现:

1 #define _CRT_SECURE_NO_WARNINGS 2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 struct Person
7 {
8 char* name;
9 int age;
10 };
11
12 //分配内存
13 struct Person** allocateSpace()
14 {
15 struct Person** temp = malloc(sizeof(struct Person*) * 3);
16 for(int i = 0; i < 3; ++i)
17 {
18 temp[i] = malloc(sizeof(struct Person));
19 temp[i]->name = malloc(sizeof(char) * 64);
20
21 sprintf(temp[i]->name, "Name_%d", i + 1);
22 temp[i]->age = 100 + i;
23 }
24 return temp;
25 }
26
27 //打印
28 void printPerson(struct Person** person)
29 {
30 if(NULL == person)
31 {
32 return;
33 }
34 for(int i = 0; i < 3; ++i)
35 {
36 printf("Name:%s Age:%d\n", person[i]->name, person[i]->age);
37 }
38 }
39
40 //释放内存
41 void freeSpace(struct Person** person)
42 {
43 if(NULL == person)
44 {
45 return;
46 }
47 for(int i = 0; i < 3; ++i)
48 {
49 if(person[i] == NULL)
50 {
51 continue;
52 }
53 if(person[i]->name != NULL)
54 {
55 printf("Name:%s的内存被释放!\n",person[i]->name)
56 free(person[i]->name);
57 person[i]->name = NULL;
58 }
59 free(person[i]);
60 person[i] = NULL;
61 }
62 free(person);
63 person = NULL;
64 }
65
66
67 void test()
68 {
69 struct Person** person = NULL;
70 person = allocateSpace();
71 printPerson(person);
72 freeSpace(person);
73 }
74
75 int main(){
76
77 test();
78
79 system("pause");
80 return EXIT_SUCCESS;
81 }


 

参考:

1)讲义:豆丁网:​​https://www.docin.com/p-2159552288.html​​​道客巴巴:​​https://www.doc88.com/p-6951788232280.html​

 

在学习c语言提高总结了笔记,并分享出来