《C语言深度刨析》整理 |
您所在的位置:网站首页 › c语言深度刨析 › 《C语言深度刨析》整理 |
指针是c/c++ 精华,没有很好的掌握指针,基本是没有掌握c/c++,对c/c++ 也是一知半解,往往指针掌握不好,也不能很好的理解数组和内存管理 一、指针在系统同占用的空间 在32位系统中,指针所占空间的大小为 4个字节,与指针指向的数据类型无关; 比如 int *p; char *p; double *p; int **p; 已经执行构造类型的结构体的(比如指向 结构体,联合体等的指针)大小均为4个字节 int *p; 表示内存中分配四个字节的内存空间,并将这块空间命名为p,并且这块内存p 中存入的数据都当做地址来看,大小是4个字节 二、int *p = NULL 和*p = NULL 区别 (1) int *p = NULL; 表示定义了一个指向int 类型的指针 p(p指向的内存用来存放int类型的数据),并将p 初始化为NULL,即p 指向内存为NULL地方(内存地址0x00000000); (2) int *p; *p = NULL; 表示定义了一个指向int类型的指针 p(p指向的内存用来存放int类型的数据),注意此处p 未被初始化,p可能指向内存中一个非法的内存地址,而这时对*p = NULL; 赋值 可能是对非法的内存地址进行赋值; 解析:区别:(1)表示对p赋值(定义p指向的内存地址)(2) p 指向未定义,p指向随机的一块内存地址,是对p指向的内存进行赋值 注意:定义变量的时候,定义的同时一定要对变量进行初始化; 三、将数值存储到指定的内存地址 现对内存为0x0012ff60 的内存进行赋值0x100(首先保证0x0012ff60这个内存地址是可以访问的) (1) int *p = (int *)0x0012ff60; *p = 0x100; (2) *((int *)0x0012ff60) = 0x100; 四、数组内存布局 int a[10] = {0}; 表示在内存中申请了一块内存 大小是 sizeof(int)*10,这块内存存放10个int 类型的数据,整块内存命名为a,这块内存没有名字,要访问内存中的各个元素只能通过数组 名a 加上下标或者偏移量的形式访问; (1) a 作为右值的时候表示数组首元素首地址; sizeof(a) = 40 (32位机下) (2) &a 表示数组的首地址, &a + 1 将偏移 sizeof(int) * 10 个大小的空间 (3)a 不能作为左值:a 的值不可改变(数组的访问要通过a加地址偏移或者下标形式访问)a 不能作为左值 比如a++ 是错误的, a++ 等价于 a = a + 1;(编译错误) int *p = NULL; p = a + 1; (编译正确) (4)&a[0]和&a 区别 虽然&a[0]和&a 值是相同的,但表示的意义不同: &a[0] 表示a[0]的地址(数组首元素首地址) &a 表示的是整个数组的首地址 五、指针和数组的关系 指针和数组无任何关系 (1)指针就是指针,指针变量在32 位系统下,永远占4 个byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方都能通过这个指针变量访问到 (2)数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数 六、指针、数组访问 看下面一道例题: view plain print ? int main() { int a[5] = {1,2,3,4,5}; int *ptr = (int *)(&a+1); printf("%d, %d", *(a+1), *(ptr-1)); return 0; }
解析: (1) &a + 1 表示自数组的首地址开始,偏移了&a + 5*sizeof(int) 大小的地址, 指向数组a[4]即 元素5 后面的元素; (int *) (&a + 1) 然后强制类型转化为指向int 类型的指针,然后赋值给 ptr,ptr的偏移量n, 就转化为偏移 n*sizeof(int) 大小 所以,*(ptr - 1) 值为 5 (2) *(a+1) 表示数组首元素首地址偏移一个 sizeof(int) 的地址, 所以*(a+1) 值为2 七、代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方定义为数组,在别的地方也只能声明为数组 八、指针数组和数组指针 (1)指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。 比如: int *p1[10]; (2)数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称 比如: int (*p2)[10]; 九、指针数组经典例题 在x86 系统下,其值为多少? view plain print ? int main() { int a[4]={1,2,3,4}; int *ptr1=(int *)(&a+1); int *ptr2=(int *)((int)a+1); printf("%x,%x",ptr1[-1],*ptr2); return 0; }解析: (1) ptr1 的值为 4 (解析见前面) (2) 首先要确定系统是大端还是小端模式 确定大端小端模式函数: 函数返回值为1:小端模式;函数返回值为0:大端模式 view plain print ? int checkSystem() { union test { int i; char ch; }a; a.i = 1; return ( a.ch == 1); } 若checkSystem() 返回值 为1,表示系统为小端模式, *ptr2 值为 2000000 若checkSystem()返回值 为0,表示系统为大端模式, *ptr2 值为 100 十、二维数组 1. 二维数组的地址 char a[3][4]; 二维数组在内存中是以线性的形式存储的,char a[i][j] ;编译器总是将二维数组看成是一个一维数组,而一维数组的每一个元素又都是一个数组 a[i][j]的首地址为&a[i]+j*sizof(char)。再把&a[i]的值用a 表示,得到a[i][j]元素的首地址为:a+ i*sizof(char)*4+ j*sizof(char)。同样,可以换算成以指针的形式表示:*(a+i)+j 2. 二维数组的初始化 view plain print ? #include int main(int argc,char * argv[]) { int a [3][2]={(0,1),(2,3),(4,5)}; int *p; p=a [0]; printf("%d",p[0]); } 解析: 结果是 1,而不是0 注意例题中初始化的时候,用到的是括号表达式而不是大括号 在初始化二维数组的时候一定要注意,别不小心把应该用的花括号写成小括号了。 十一、二级指针 char **p; 定义了一个二级指针变量p。p 是一个指针变量,在32 位系统下占4 个byte。它与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地址。 注: 多维数组和多级指针可以根据二维数组和二级指针依次类推 十二、数组参数和指针参数 1. 不能向函数传递一个数组 比如: view plain print ? #include "stdio.h" void fun(char a[10]) { ...... } int main() { char b[10] = "abcdefg"; fun(b[10]); return 0; }解析: (1):b[10] 代表的是数组的一个元素,不能代表数组 (2):b[10] 数组越界了 (3):void fun(char a[10]); 实际是要传递的是一个char类型的指针;参数可以改写为 void fun(char a[]); 或者 void fun(char *a); (4):函数调用的时候可以改写成 fun(b); 总结:C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。 2. 不能把指针本身传递给函数 例一: view plain print ? #include "stdio.h" void fun(char *p) { char c = *(p+3); printf("%c\n", c); } int main() { char *p2 = "abcdefg"; fun(p2); return 0; }解析: (1). p2 是main 函数内的一个局部变量,它只在main 函数内部有效。(注意:main 函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局 变量 一样长而已。全局变量一定是定义在函数外部的) (2). 对实参p2做一份拷贝并传递给被调用的函数。即对p2 做一份拷贝,假设其拷贝名为_p2。那传递到函数内部的就是_p2 而并非p2 本身。 例二: view plain print ? #include "stdio.h" #include "stdlib.h" #include "string.h" void GetMemory(char * p, int num) { p = (char *)malloc(num * sizeof(char)); } int main() { char *str = NULL; GetMemory(str, 10); strcpy(str, "hello"); free(str);//free 并没有起作用,内存泄漏 return 0; }解析: 通过编译可以看到,str的值仍未NULL。因为str 传到函数内部的是str的拷贝假设名字是_str, 当函数GetMemory(char *p, int num) 退出时,申请的内容 同时释放掉并未传递给str(malloc 的内存的地址并没有赋给str,而是赋给了_str。而这个_str 是编译器自动分配和回收的),导致内存访问异常; 例二:解决方法 (1) 增加return,将申请的内存返回给str,同时要用str 进行接收 view plain print ? #include "stdio.h" #include "stdlib.h" #include "string.h" char * GetMemory(char * p, int num) { p = (char *)malloc(num * sizeof(char)); return p; } int main() { char *str = NULL; str = GetMemory(str, 10); strcpy(str, "hello"); free(str); return 0; }(2)使用二级指针 view plain print ? #include "stdio.h" #include "stdlib.h" #include "string.h" void GetMemory(char ** p, int num) { *p = (char *)malloc(num * sizeof(char)); } int main() { char *str = NULL; GetMemory(&str, 10); strcpy(str, "hello"); puts(str); free(str); return 0; }解析: 这种方法真正的将str传到函数GetMemory(char **p, int num) 中 (1)GetMemory(&str, 10); 这里传递的是 str的地址,函数内部申请空间赋值给 *p(也就是str),malloc 分配的内存地址是真正赋值给了str 3. 二维数组参数和二维指针参数 void fun(char a[3][4]); 可以把a[3][4]理解为一个一维数组a[3],其每个元素都是一个含有4 个char 类型数据的数组。上面的规则,“C 语言中,当一维数组作为函数参数的时候,编译器总是把它解 析成一个指向其首元素首地址的指针。所以上面表达式可以改为: void fun(char a[ ][4]); 或者 void fun(char (*a)[4]); C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如 此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写 十三、函数指针 函数指针的形式之一: char * (*fun1)(char * p1,char * p2); (注意:此处的fun1 是指向函数的指针,而不是函数名,此函数是匿名的) 1、函数指针的使用 比如: view plain print ? #include #include char * fun(char * p1, char * p2) { int i = 0; i = strcmp(p1, p2); if (0 == i) { return p1; } else { return p2; } } int main() { char *str = NULL; char * (*pf)(char * p1, char * p2); pf = &fun; str = (*pf)("aa", "bb"); puts(str); return 0; }解析: 使用指针的时候,需要通过钥匙(*)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。这里需要注意到 是,在Visual C++6.0 里,给函数指针赋值时,可以用&fun 或直接用函数名fun。因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。 2. (*(void(*) ())0)() 的含义 解析: (1)void(*)() 定义了一个函数指针 (2)(void(*)()0) 将0强制转化为一个函数指针,0是个地址,这个匿名函数保存于起始地址为0的一块内存中 (3)(*(void(*) ())0),这是取0 地址开始的一段内存里面的内容,其内容就是保存在首地址为0 的一段区域内的函数 (4)(*(void(*) ())0)(),这是函数调用 3. 函数指针数组 char * (*pf[3])(char * p); 解析: pf 是数组名,这个数组是指针数组,数组中的每个元素均为指针(指向函数的指针) 例题:指针数组的使用 view plain print ? #include #include char * fun1(char * p) { printf("%s\n",p); return p; } char * fun2(char * p) { printf("%s\n",p); return p; } int main() { char * (*pf[3])(char * p); pf[0] = fun1; // 可以直接用函数名 pf[1] = &fun2; // 可以用函数名加上取地址符 pf[0]("fun1"); pf[1]("fun2"); return 0; }
|
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |