C和指针之数组和函数部分总结
2017-11-16 02:57
295 查看
函数和数组2个章节部分学习总结
1、之前对函数参数传递指针没有理解到位,以为像传其它的数据一样,只是数据的一份拷贝,然后不修改原始的数据,函数参数如果是传递的指针,传递给函数是指针的一份拷贝,可以通过指针间接访问数据,从而得到修改原始数据,反正不能把指针变量本身传递给一个函数,
如果把这个函数里面的进行在堆区分配内存空间,只不过是指针的拷贝的这个指针分配了内存,如果不返回,编译器会自动分配和回收,无法使用,实际指针还是没有分配内存,如果想要想要给实际指针分配内存,我们可以在函数里面分配内存了然后return这个指针,或者用二级指针作为参数也行,二级指针分配的内存是真正给了指针变量本身,然后我们绝对不可以在函数返回堆区的指针,因为函数退出时,它的栈也就被清除了,之后其内容会被别的局部变量、函数调用保存的上下文等信息替换掉,所以返回的这个栈偏移就失去意义了。
2、之前不知道在函数参数中,声明数组可以不指定长度,不声明是合法的,因为函数不为数组元素分配内存,调用时把实参数组名里面的常量地址传给形参数组名,就是说形参数组名保存的就是实参数组名里面存的地址。这样在被调用函数内部,形参数组就指向了和实参数组名指向的同一个数组。
3、函数递归调用自己的时候,比如递归调用自己的时候下面还有值打印,这个值应该是当前调用的这个函数的值,而不是之前挂载的值,之前我这里理解不太深刻,后面在答辩文档中会给出相应的Demo.
4、之前不知道递归的开销非常大,因为返回调用自己会为局部变量分配内存空间,一般还是少用。
5、之前不知道C语言里面有变函数参数列表,在stdarg宏中,有va_list类型和va_start、va_arg、va_end宏,后面会在答辩文档中给出简单实现my_printf(“***”, ...);函数的实现
void va_start(va_list ap, last);// 取第一个可变参数的指针给ap,last是函数声明中的最后一个固定参数(比如printf函数原型中的*fromat);
type va_arg(va_list ap, type); // 返回当前ap指向的可变参数的值,然后ap指向下一个可变参数,type表示当前可变参数的类型(支持的类型位int和double;
void va_end(va_list ap); // 将ap置为NULL
6、我之前对“数组名++”这样操作,这样是错误的,然后不理解”&数组名+1“的含义,而且对sizeof(数组名)为什么返回时整个数组的大小不明白为什么?数组名的值是个指针常量,也就是数组第一个元素的地址,所以不能 “数组名++”这样操作,既然是指针常量,那么sizeof应该是指针的大小,64位操作系统就是8了,感觉理论是这样的,但是我们平时写代码的时候,sizeof结果返回的整个数组长度,一时也不知道为什么会这样,然后看了《C和指针》数组这个章节,里面介绍如下,在以下两中场合下,数组名并不是用指针常量来表示,就是当数组名作为sizeof操作符和单目操作符&的操作数时。
sizeof返回整个数组的长度,而不是指向数组的指针的长度。 取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针,所以sizeof返回整个数组的长度,当数组名作为函数参数传递给函数的时候,数组名就是一个指向第一个元素的指针,所以此时传递的是函数一份该指针的拷贝,所以这个时候在函数里面求这个用sizeof求这个指针的大小,很明显不是数组的长度了,就是普通指针的长度了,我的笔记本是64位操作系统,所以指针的大小是8,然后“&数组名 + 1”,也就意味着指针指向了下一个数组的地址,也就是这个数组最后一个元素的下一个地址,后续这些对比比较会在答辩文档中给出Demo,然后分析结果。
7、既然不能 数组名++ 这样操作,但是把数组名传递给函数参数的时候,在函数内部又可以执行 数组名++ 这样的操作,一开始我一直想不明白,可以这样理解,数组名的值就是一个指向数组第一个元素的指针,然后传递给函数的时候,就是这个指针的拷贝,如果执行了下标引用,实际上是对这个指针执行简介访问操作,可以访问和修改程序的数组元素,或者说当一维数组作为函数参数的时候,编译器总是把它解析成一个指向成一个指向其首元素首地址的指针。
8、之前以为初始化char message[] = {'c', 'h', 'e', 'n'}和char message[] = "chen"有区别,其实没有区别之第一种的另外一种写法,定义之后实在栈区分配了内存空间的,然后char *p = "chen"; 这个“chen”存储在常量区,指针变量p被初始化指向这个字符串常量的存储位置。
9、之前对二位数组的下标理解还很局限,一直以为第一个下标就是二位数组有多少横,第二个下标就是这个二位数组有多少列,比如a[3]可以理解为在内存中,连续的3个位置,每个位置存储了一个元素,a[3][6]可以理解为这3个位置,每个位置又包含了6个元素的数组,
10、我之前对二位数组名理解以为和一位数组名一样,其实错了,一维数组名的值是指针常量,类型是”指向元素类型的值“,它指向数组的第一个元素,上面的理解a[3][6]可以理解为3个元素,每个元素包含6个元素的整形数组。所以这里a指向第一个元素的指针,所以这里
二位数组a是指向一个包含6个整形元素的数组指针。
11、之前只知道二位数组作为函数参数传递的时候一般用f(int a[][10])这种形式,因为二维数组名是指向数组指针,所以我们一般用"数组指针"来传递,比如f(int (*p)[10]),这种模式作为传递参数,比如这里是p是指向包含10个元素数组的指针,我们在函数里面可以默认和二位数组名等效使用,通过下标或者解引用来操作二维数组,但是我们不能用参数形式为二级指针来传递数组名,比如f(int **p)这里我之前也有点疑惑,它是装整形指针的指针,和指向整形数组的指针不是一回事,所以不能用,函数参数是二级指针,可以传递实参“指针数组”的数据类型,然后还深入对比学习了“指针数组”,比如int
*p[10],对比“数组指针”来学习,Demo在答辩文档里面给出,
这里先总结,实参和形参一般匹配总结。
实际参数 所匹配的形参
数组的数组 char c[8][10]; char (*c)[10];
指针数组 char *c[10]; char **c;
数组指针 char (*c)[10]; char (*c)[10];
指针的指针 char **c; char **c;
12、之前不知道C语言里面有auto关键字(内存栈),auto关键字在函数的局部变量前编译器会默认加上,意味着当前变量会在内存栈上进行分配,在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,
在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的,当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,
程序由该点继续运行。
13、之前不知道有register关键字,我之前对内存、寄存器、cpu之间的者关系不清晰,应该是数据从内存中取出给寄存器,然后CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道,寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多,在变量前面加上register就是寄存器变量,在大量频繁的操作时使用寄存器变量,这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率
14、然后对volatile关键字理解不深刻,只知道java很多地方用到这个关键字
volatile
比如
int i = 0; //volatile int i = 0;
int j = i; //(1)语句
int k = i; //(2)语句
在(1)(2)中,i没有被作为左值,这个从内存中取出i的值赋值给j,但是这个代码没有丢掉,,而是在(2)中语句中继续用这个值给k赋值,编译器不会从生成的汇编代码里面重新从内存i的值,如果加上volatile关键字,volatile告诉编译器,i是谁随时可以改变,每次使用它的时候必须从内存中取出i的值,所以加了volatile修饰的变量,简单理解就是每次使用这个变量都到内存中取。
15、const修饰变量之前搞混淆了,总结如下
const int *p; //p可变,p指向的对象不可变
int const *p; //p可变,p指向的对象不可变
int *const p; //p不可以变,p指向的对象可变
const int *const p; //p不可以变,p指向的对象不可变
16、之前不知道内存为0的地址,其实就是NULL地址处,任何指针变量刚被创建时不会自动成为NULL指针,所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存,没有初始化乱指,可能会成野指针,使用完指针也将指针变量值设置为 NULL,free和delete之后,只把指针所指的内存给释放掉了,但是没有把指针本身干掉,此时指针指向的就是“垃圾”内存,也需要置为NULL,防止野指针.(野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL)
17、之前很少用assert宏,
函数的入口处用assert宏作入口校验
一般在函数入口处使用assert(NULL != p)对参数进行校验,在非参数的地方使用if(NULL != p)来校验,但是有个要求,是p在定义的同时被初始化为NULL,如果p指针没有被初始化为NULL,其内部是一个非NULL的乱码
#include <stdio.h>
#include <assert.h>
int main()
{
char *p = NULL;
// char c = 'a';
// p = &c;
assert(p != NULL);
printf("hello");
return 0;
}
18、通过做课后八皇后习题,熟悉了回溯的算法思想。
1、之前对函数参数传递指针没有理解到位,以为像传其它的数据一样,只是数据的一份拷贝,然后不修改原始的数据,函数参数如果是传递的指针,传递给函数是指针的一份拷贝,可以通过指针间接访问数据,从而得到修改原始数据,反正不能把指针变量本身传递给一个函数,
如果把这个函数里面的进行在堆区分配内存空间,只不过是指针的拷贝的这个指针分配了内存,如果不返回,编译器会自动分配和回收,无法使用,实际指针还是没有分配内存,如果想要想要给实际指针分配内存,我们可以在函数里面分配内存了然后return这个指针,或者用二级指针作为参数也行,二级指针分配的内存是真正给了指针变量本身,然后我们绝对不可以在函数返回堆区的指针,因为函数退出时,它的栈也就被清除了,之后其内容会被别的局部变量、函数调用保存的上下文等信息替换掉,所以返回的这个栈偏移就失去意义了。
2、之前不知道在函数参数中,声明数组可以不指定长度,不声明是合法的,因为函数不为数组元素分配内存,调用时把实参数组名里面的常量地址传给形参数组名,就是说形参数组名保存的就是实参数组名里面存的地址。这样在被调用函数内部,形参数组就指向了和实参数组名指向的同一个数组。
3、函数递归调用自己的时候,比如递归调用自己的时候下面还有值打印,这个值应该是当前调用的这个函数的值,而不是之前挂载的值,之前我这里理解不太深刻,后面在答辩文档中会给出相应的Demo.
4、之前不知道递归的开销非常大,因为返回调用自己会为局部变量分配内存空间,一般还是少用。
5、之前不知道C语言里面有变函数参数列表,在stdarg宏中,有va_list类型和va_start、va_arg、va_end宏,后面会在答辩文档中给出简单实现my_printf(“***”, ...);函数的实现
void va_start(va_list ap, last);// 取第一个可变参数的指针给ap,last是函数声明中的最后一个固定参数(比如printf函数原型中的*fromat);
type va_arg(va_list ap, type); // 返回当前ap指向的可变参数的值,然后ap指向下一个可变参数,type表示当前可变参数的类型(支持的类型位int和double;
void va_end(va_list ap); // 将ap置为NULL
6、我之前对“数组名++”这样操作,这样是错误的,然后不理解”&数组名+1“的含义,而且对sizeof(数组名)为什么返回时整个数组的大小不明白为什么?数组名的值是个指针常量,也就是数组第一个元素的地址,所以不能 “数组名++”这样操作,既然是指针常量,那么sizeof应该是指针的大小,64位操作系统就是8了,感觉理论是这样的,但是我们平时写代码的时候,sizeof结果返回的整个数组长度,一时也不知道为什么会这样,然后看了《C和指针》数组这个章节,里面介绍如下,在以下两中场合下,数组名并不是用指针常量来表示,就是当数组名作为sizeof操作符和单目操作符&的操作数时。
sizeof返回整个数组的长度,而不是指向数组的指针的长度。 取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针,所以sizeof返回整个数组的长度,当数组名作为函数参数传递给函数的时候,数组名就是一个指向第一个元素的指针,所以此时传递的是函数一份该指针的拷贝,所以这个时候在函数里面求这个用sizeof求这个指针的大小,很明显不是数组的长度了,就是普通指针的长度了,我的笔记本是64位操作系统,所以指针的大小是8,然后“&数组名 + 1”,也就意味着指针指向了下一个数组的地址,也就是这个数组最后一个元素的下一个地址,后续这些对比比较会在答辩文档中给出Demo,然后分析结果。
7、既然不能 数组名++ 这样操作,但是把数组名传递给函数参数的时候,在函数内部又可以执行 数组名++ 这样的操作,一开始我一直想不明白,可以这样理解,数组名的值就是一个指向数组第一个元素的指针,然后传递给函数的时候,就是这个指针的拷贝,如果执行了下标引用,实际上是对这个指针执行简介访问操作,可以访问和修改程序的数组元素,或者说当一维数组作为函数参数的时候,编译器总是把它解析成一个指向成一个指向其首元素首地址的指针。
8、之前以为初始化char message[] = {'c', 'h', 'e', 'n'}和char message[] = "chen"有区别,其实没有区别之第一种的另外一种写法,定义之后实在栈区分配了内存空间的,然后char *p = "chen"; 这个“chen”存储在常量区,指针变量p被初始化指向这个字符串常量的存储位置。
9、之前对二位数组的下标理解还很局限,一直以为第一个下标就是二位数组有多少横,第二个下标就是这个二位数组有多少列,比如a[3]可以理解为在内存中,连续的3个位置,每个位置存储了一个元素,a[3][6]可以理解为这3个位置,每个位置又包含了6个元素的数组,
10、我之前对二位数组名理解以为和一位数组名一样,其实错了,一维数组名的值是指针常量,类型是”指向元素类型的值“,它指向数组的第一个元素,上面的理解a[3][6]可以理解为3个元素,每个元素包含6个元素的整形数组。所以这里a指向第一个元素的指针,所以这里
二位数组a是指向一个包含6个整形元素的数组指针。
11、之前只知道二位数组作为函数参数传递的时候一般用f(int a[][10])这种形式,因为二维数组名是指向数组指针,所以我们一般用"数组指针"来传递,比如f(int (*p)[10]),这种模式作为传递参数,比如这里是p是指向包含10个元素数组的指针,我们在函数里面可以默认和二位数组名等效使用,通过下标或者解引用来操作二维数组,但是我们不能用参数形式为二级指针来传递数组名,比如f(int **p)这里我之前也有点疑惑,它是装整形指针的指针,和指向整形数组的指针不是一回事,所以不能用,函数参数是二级指针,可以传递实参“指针数组”的数据类型,然后还深入对比学习了“指针数组”,比如int
*p[10],对比“数组指针”来学习,Demo在答辩文档里面给出,
这里先总结,实参和形参一般匹配总结。
实际参数 所匹配的形参
数组的数组 char c[8][10]; char (*c)[10];
指针数组 char *c[10]; char **c;
数组指针 char (*c)[10]; char (*c)[10];
指针的指针 char **c; char **c;
12、之前不知道C语言里面有auto关键字(内存栈),auto关键字在函数的局部变量前编译器会默认加上,意味着当前变量会在内存栈上进行分配,在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,
在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的,当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,
程序由该点继续运行。
13、之前不知道有register关键字,我之前对内存、寄存器、cpu之间的者关系不清晰,应该是数据从内存中取出给寄存器,然后CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道,寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多,在变量前面加上register就是寄存器变量,在大量频繁的操作时使用寄存器变量,这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率
14、然后对volatile关键字理解不深刻,只知道java很多地方用到这个关键字
volatile
比如
int i = 0; //volatile int i = 0;
int j = i; //(1)语句
int k = i; //(2)语句
在(1)(2)中,i没有被作为左值,这个从内存中取出i的值赋值给j,但是这个代码没有丢掉,,而是在(2)中语句中继续用这个值给k赋值,编译器不会从生成的汇编代码里面重新从内存i的值,如果加上volatile关键字,volatile告诉编译器,i是谁随时可以改变,每次使用它的时候必须从内存中取出i的值,所以加了volatile修饰的变量,简单理解就是每次使用这个变量都到内存中取。
15、const修饰变量之前搞混淆了,总结如下
const int *p; //p可变,p指向的对象不可变
int const *p; //p可变,p指向的对象不可变
int *const p; //p不可以变,p指向的对象可变
const int *const p; //p不可以变,p指向的对象不可变
16、之前不知道内存为0的地址,其实就是NULL地址处,任何指针变量刚被创建时不会自动成为NULL指针,所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存,没有初始化乱指,可能会成野指针,使用完指针也将指针变量值设置为 NULL,free和delete之后,只把指针所指的内存给释放掉了,但是没有把指针本身干掉,此时指针指向的就是“垃圾”内存,也需要置为NULL,防止野指针.(野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL)
17、之前很少用assert宏,
函数的入口处用assert宏作入口校验
一般在函数入口处使用assert(NULL != p)对参数进行校验,在非参数的地方使用if(NULL != p)来校验,但是有个要求,是p在定义的同时被初始化为NULL,如果p指针没有被初始化为NULL,其内部是一个非NULL的乱码
#include <stdio.h>
#include <assert.h>
int main()
{
char *p = NULL;
// char c = 'a';
// p = &c;
assert(p != NULL);
printf("hello");
return 0;
}
18、通过做课后八皇后习题,熟悉了回溯的算法思想。
相关文章推荐
- 指针数组、数组指针、函数指针、指针函数总结
- 数组、字符串在指针和函数中的一些经验总结
- 指针数组、数组指针、函数指针、指针函数总结C++
- 第十章 数组和指针(函数和二维数组,本章个人总结)总结 140520
- 函数指针与函数指针数组应用总结
- C语言指针,数组,函数总结
- PHP中与数组有关的函数总结
- PHP 数组函数 内部指针
- C/C++对于函数指针数组的进一步解释
- 入一个整数数组,实现一个函数,来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,所有偶数位于数组的后半部分。
- 指针数组、数组指针、函数指针、指针函数总结
- 什么是指针?关于指针的数组,数组指针,函数指针,函数指针数组,指向函数指针数组的指针的理解
- 一劳永逸:关于C/C++中指针、数组与函数复合定义形式的直观解释
- 总结函数的参数,指针参数及const修饰的情况
- 返回数组指针的函数
- 黑马程序员 java基础 函数 数组 查找与排序总结
- 指针和数组(总结)
- 理解数组,结构 ,函数指针,指针函数,数组指针,指针数组,结构指针的定义和实现
- sql中常用不常见函数部分总结