C和指针之数组和函数部分总结

来源:互联网 发布:mac下载office办公软件 编辑:程序博客网 时间:2024/05/22 13:58
函数和数组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、通过做课后八皇后习题,熟悉了回溯的算法思想。