Summary
1)指针的实质
是变量,非凡之处在于指针存储的值是内存地址
(内存中每个存储单元的编号
:计算机中的最小存储单元是1byte ,即每个字节的编号都是一个内存地址)
2)程序中的所有元素都存在于内存中,因而能够通过内存地址拜访程序元素
3)内存地址的实质
是一个无符号整数(4字节或8字节);4字节和8字节别离对应于32位和64位,所以咱们常说的32位和64位零碎指的就是可拜访的最大内存地址
是多少。因为64位零碎能反对更大的内存寻址,所以64位零碎会比32位零碎能同时运行的程序要多。
4)只有通过内存地址+长度
能力确定一个变量中保留的值。内存地址只是一个起始值,只有依据类型信息
,晓得占多少内存、解读形式,才能够精确的读出变量里保留的值。
5)函数参数是指针时(传址调用
),能够实现在函数外部批改函数内部变量的值;应用指针作为参数,能够作为一个传出参数
,使得函数能返回多个值。
6)对于数组int a[5],a和&a的关系
:
- a能够
看做
一个常量指针,示意数组首元素的地址,值是0xA,类型是int*,即内存长度为4 - &a是数组的地址,值是0xA,类型是int(*)[5],即内存长度为20。指向数组的指针:
int(*pArr)[5] = &a;
7)指针与数组的等价用法:
int a = {1, 2, 3, 4, 5};int* p = a;a[i] <--> *(a + i) <--> *(p + i) <--> p[i]
8)C语言中,字符串常量的类型是char*
(c++里则是const char*);int v = *p++;等价于int v = *p; p++
;因为(*)的优先级和(++)雷同
9)函数的实质
是一段内存中的代码,占用一段间断的内存
。函数名就是函数的入口地址
(函数体代码的起始地址)。通过函数名调用
函数,实质为指定具体地址的跳转执行
。因而能够定义指针,保留函数的入口地址:type (*pFunc)(param) = funcName;
10)问:既然通过函数名能够间接调用函数,那么为什么还须要函数指针呢?
答:能够定义函数指针参数
。应用雷同的代码,实现不同的性能
。主调函数只晓得函数原型是这样,但不晓得具体会调用哪个函数。函数指针
是回调函数
的实现机制:函数作为参数应用时,就形成了callback;
11)堆空间的实质是程序备用的“内存仓库”
,以字节为单位
预留的可用内存
12)在C语言中,void*
指针能够和其余类型的指针type*
进行互相赋值
,因为C语言对指针的查看并不严格。然而在应用时肯定要留神操作的数据类型,void*仅仅是一个初始地址,不蕴含长度信息
。
13)malloc用于向堆空间中以字节为单位申请内存;free用于偿还堆空间的内存,malloc而不free,内存泄露;屡次free,解体。
14)指针是一个变量,那么天然也就会有指向指针的指针,即多级指针
。之前变量能够应用指针进行传址调用,那么也能够通过多级指针实现对指针的传址调用,在函数外部扭转内部的指针的值。
15)二维数组的实质是一维数组
,所以二维数组名a示意的是数组的首元素,即a[0]示意的是一个一维数组;a的类型是type (*)[size2]
int arr[2] = {0};int* pa = arr;int (*pArr)[2] = &arr;int bArr[2][2] = {0};int (*pb)[2] = bArr;typedef int (oneDimension)[2];oneDimension (*pbArr)[2] = &bArr;
16)禁止
返回局部变量的地址,因为局部变量在函数调用完结后,生命期就完结了。此时返回的是个野指针
。
1、指针
1.1 指针实质
指针实质
是C语言中的变量
- 因为是变量,所以
用于保留具体值
- 非凡之处,指针
保留的值是内存中的地址
什么是内存地址?
- 内存是计算机中的存储部件,每个存储单元有固定惟一的编号(每个存储单元:1byte)
- 内存中
存储单元的编号
即内存地址
程序中的所有元素都存在于内存中,因而能够通过内存地址拜访程序元素。
获取地址:
- C语言中通过&操作符获取程序元素的地址
- &能够取得变量、函数、数组的起始地址
内存地址的
实质是一个无符号整数
(4或8字节)int var = 0;printf("var address = %p\n", &var);
1.2 指针语法
指针定义语法:type * pointer
- type - 数据类型,
决定拜访内存时的长度
范畴 *
- 标记,意味着定义一个指针变量- pointer - 变量名,遵循C语言命名规定
指针内存拜访:* pointer
- 指针拜访操作符(*)作用于指针变量即可
拜访内存数据
- 指针的类型决定通过地址拜访内存时的长度范畴
- 指针的类型对立占用4字节或8字节
初学指针时的军规:
type*
类型的指针只能保留type
类型变量的地址(如float和int类型的变量,都是4个字节,然而内存中的二进制是不一样的,如果存的是int,以float的形式来读,必定是不对的)禁止
不同类型的指针互相赋值禁止
将一般数值当做地址赋值给指针- 留神:指针保留的地址必须是无效地址
问题:之前在函数里替换两个变量的值,真的没方法实现么?
思考:想要替换两个变量的值,必须在函数外部批改函数数内部的变量?如果参数是指针呢?把变量的地址传进去,在函数里间接通过内存地址去批改地址是否可行?
void swap(int* a, int* b){ int t = *a; *a = *b; *b = t;}
通过传指针就实现了在函数外部批改函数内部的变量。另外,函数的return只能返回一个值,如果在参数中应用指针,这样函数就能够返回多个值,传出参数
。
2、指针与数组
问题:数组的实质是一段间断的内存,那么,数组的地址是什么?如何获取?
2.1 指针与一般数组
- 应用取地址符&取得数组的地址:&a,&a示意一个地址(数组的地址),值是0xA,类型是
int(*)[5]
,即长度是20。指向数组的指针:int(*pName)[5] = &a; - 数组名可看一个常量指针:a,a代表一个地址(数组首元素的地址),值是0xA,类型是
int*
,即长度是4 当指针指向数组元素时,能够进行指针运算(指针挪动)
int a[] = {1, 2, 3, 4, 5};int* p = a;p = p + 1;
留神:数组名只是能够看做一个指针,代表了0号元素的地址。
指针与数组的等价用法:
int a = {1, 2, 3, 4, 5};int* p = a;a[i] <--> *(a + i) <--> *(p + i) <--> p[i]
2.2 指针与字符串
字符串拾遗
- 问:C语言中的字符串常量是什么类型?
答:
char*
类型,一种指针类型printf("%p\n", "GrandSoft");
指针挪动组合拳:int v = *p++;
- 指针拜访操作符(*)和自增运算符(++)优先级雷同
- 所以,先从p指向的内存中取值,而后p进行挪动
- 等价于:int v = *p; p++;
3、指针与函数
问题:函数调用时会跳转到函数体对应的代码处执行,那么,如何晓得函数体代码的具体位置?
深刻函数之旅:
- 函数的
实质
是一段内存中的代码(占用一段间断内存) 函数领有类型,
函数类型
由返回类型和参数类型列表组成函数申明 类型 int sum(int n) int(int) void g(void) void(void)
函数指针:type func(type1 a, type2 b)
- 函数名即函数入口地址,类型为
type (*)(type1, type2)
- 对于func的函数,func和&func
数值雷同
,意义雷同
指向函数的指针:type (*pFunc)(type1, type2) = func;
int (*pFunc)(int a, int b) = nullptr;pFunc = add;printf("%d\n", pFunc(1, 2)); // 1)printf("%d\n", (*pFunc)(1, 2)); // 2)
对于两种调用办法的了解:
1)函数指针保留函数的入口地址,函数名也保留了函数的入口地址,函数名能够间接调用函数,那么用pFunc间接调用函数,也很正当
2)pFunc是一个地址,外面寄存的函数的代码,应用*pFunc就取到了函数体,间接调用函数,也很正当
问题:既然能够通过函数名间接调用函数,那么为什么还须要应用函数指针呢?
答:这样就能够应用函数指针作为函数参数
。
- 函数指针的实质还是指针(变量,保留地址)
能够定义函数指针参数,
应用雷同代码实现不同性能
int calculate(int a[], int len, int(*cal)(int, int)){ int ret = a[0]; int i = 0; for(i=1; i<len; i++) { ret = cal(ret, a[i]); } return ret;}
依据传入不同的cal函数,能够实现比方数组相乘、相加、相除等等。在calculate里,
只晓得要调用的函数原型是这样
,然而具体调用的是什么函数
,执行了什么性能,只有执行到cal才晓得
。
4、指针与堆空间
再论内存空间:内存区域不同,用处不同
- 全局数据区:寄存全局变量,动态变量
- 栈空间:寄存函数参数,局部变量
- 堆空间:用于动态创建变量(数组)
堆空间的实质:
备用的“内存仓库”
,以字节为单位预留的可用内存- 程序可在须要时从“仓库”中申请应用内存(
动静借
) - 当不须要再应用申请的内存时,须要及时偿还(
动静还
)
准备常识-void*
- void类型是根底类型,对应的指针类型为void*
- void*是指针类型,其指针变量可能保留地址
- 通过void*指针
无奈获取内存中的数据
(无长度信息)
堆空间的应用:
- 工具包:stdlib.h
- 申请:void* malloc(unsigned bytes)
- 偿还:void free(void* p),p必须是堆空间中的地址
堆空间的应用准则:
- 有借有还,再接不难
- malloc申请内存后,应该
判断是否申请胜利
- free只能申请开释到的内存,且
不可屡次开释
5、指针问题分析
5.1 多级指针
能够定义指针的指针来保留其余指针变量的地址
type v;type* pv = &v;type** ppv = &pv;type*** ppv = &ppv;...
应用指针能够取到指针的地址,那么就能够在函数外部,扭转指针里保留的值。
指针的传址调用,批改函数外的指针的值:
int getDouble(double** pp, unsigned int n){ int ret = 0; double* pd = (double*)malloc(sizeof(double) * n); if(pd != NULL) { *pp = pd; ret = 1; } return ret;}
5.2 二维数组
问题:一维数组名的类型为type,那么二维数组名的类型是不是type*?
再论二维数组:
- 二维数组的实质是一维数组,即:数组中的元素是一维数组
因而:二维数组a[][],a示意a[0],首元素是一个一维数组
int b[][2] = {1, 2, 3, 4};int(*pnb)[2] = b;
本文总结自“狄泰软件学院”唐佐林老师《C语言入门课程》。
如有错漏之处,恳请斧正。