c语言提高学习笔记——02-c提高04day
在学习c语言提高总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
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}, //二维数组名是什么类型?int(*)[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));//8 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 }
》扩展:常用的几种传参
void printBiArray1(int(*parr)[3], int len1, int len2) { for(int i = 0; i < len1; ++i) { for(j = 0; j < len2; ++j) { //printf("%d ", *(*(parr + i) + j)); printf("%d ", parr[i][j]);//可读性强 } } } void printBiArray2(int parr[3][3], int len1, int len2) { for(int i = 0; i < len1; ++i) { for(j = 0; j < len2; ++j) { //printf("%d ", *(*(parr + i) + j)); printf("%d ", parr[i][j]);//可读性强 } } } void printBiArray3(int parr[][3], int len1, int len2) { for(int i = 0; i < len1; ++i) { for(j = 0; j < len2; ++j) { //printf("%d ", *(*(parr + i) + j)); printf("%d ", parr[i][j]);//可读性强 } } } typedef int(*ARR_TYPE)[3]; void printBiArray4(ARR_TYPE parr, int len1, int len2) { for(int i = 0; i < len1; ++i) { for(j = 0; j < len2; ++j) { //printf("%d ", *(*(parr + i) + j)); printf("%d ", parr[i][j]);//可读性强 } } } typedef int(ARR_TYPE)[3]; void printBiArray5(ARR_TYPE *parr, int len1, int len2) { for(int i = 0; i < len1; ++i) { for(j = 0; j < len2; ++j) { //printf("%d ", *(*(parr + i) + j)); printf("%d ", parr[i][j]);//可读性强 } } }
练习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(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 }
赋值报错?分析?
解决:
(2)结构体嵌套一级指针
练习:
代码实现:
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语言提高总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
posted on 2020-06-06 12:37 Alliswell_WP 阅读(379) 评论(0) 编辑 收藏 举报