《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)数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数
六、指针、数组访问
看下面一道例题:
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 系统下,其值为多少?
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:大端模式
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. 二维数组的初始化
#include <stdio.h> 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. 不能向函数传递一个数组
比如:
#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. 不能把指针本身传递给函数
例一:
#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 本身。
例二:
#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);//<span style="color:#000099;">free 并没有起作用,内存泄漏</span> return 0; }解析:
通过编译可以看到,str的值仍未NULL。因为str 传到函数内部的是str的拷贝假设名字是_str, 当函数GetMemory(char *p, int num) 退出时,申请的内容
同时释放掉并未传递给str(malloc 的内存的地址并没有赋给str,而是赋给了_str。而这个_str 是编译器自动分配和回收的),导致内存访问异常;
例二:解决方法
(1) 增加return,将申请的内存返回给str,同时要用str 进行接收
#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)使用二级指针
#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、函数指针的使用
比如:
#include <stdio.h> #include <string.h> 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 是数组名,这个数组是指针数组,数组中的每个元素均为指针(指向函数的指针)
例题:指针数组的使用
#include <stdio.h> #include <string.h> 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; }网址:《C语言深度刨析》整理 https://www.yuejiaxmz.com/news/view/214168
相关内容
c语言printf输出格式陕西选调生言语理解:行测词语辨析角度之搭配对象
C语言学习
【深度学习】深度学习语音识别算法的详细解析
剖析C语言中a=a+++++a的无聊问题
C语言个人财务管理示例
C语言学习错题集(一)
语言C++之循环结构
彻底优化:电脑C盘深度清理指南
【时间序列管理秘籍】:xts数据包基础及深度解析