目录
初阶
1. 指针是什么
C代码中的变量,函数等在运行时要在内存上开辟空间。
而平时口语中所说的指针,通常指的是指针变量,是用来存放内存地址的变量,属于C语言的内置数据类型。
内存地址是内存中一个最小单元的编号,经过仔细的计算和权衡,发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是1或者0.
那么32根地址线产生的地址就会是:
所以:在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64根地址线,那一个指针变量的大小是8个字节,才能存放一个地 址 。
紧接着,就可以通过&(取地址操作符)取出对象的内存起始地址,把它存放到一个变量中,这个变量就是指针变量,然后通过 *(解引用操作符)就可以找到并访问或编辑对象的数据。指针变量里的数据在解引用时都会被当成地址处理。
定义方法:指向数据的类型* 指针变量名 = &对象或NULL
使用示例:
2.指针类型
通过上面的示例我们发现:指针也是有类型的。
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
......
那指针类型的意义是什么?
2.1.指针+-整数
指针的类型决定了指针向前或者向后走一步有多大(距离)。
如下示例:
2.2.指针解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
如下示例:
3. 野指针
3.1.野指针的成因
1.指针未初始化
2.指针越界访问
3.动态申请的空间释放后指针未置为NULL ,造成野指针
3.1.如何规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免函数返回局部变量的地址
5. 指针使用之前检查有效性
4. 指针运算
4.1.指针+-整数
如上示例,不赘述。
4.2.指针 - 指针
得到数值的绝对值是:两指针之间的元素个数。(前提是两指针同类型且指向同一块内存)
比如,模拟实现strlen:
int my_strlen(char *s)
{ char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
4.3.指针的关系运算
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
数组名表示的是数组首元素的地址。
2种情况除外:
1. sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小
2. &数组名,这里的数组名也表示整个数组,这里取出的是整个数组的地址
其次:p[i] 访问数组的本质就是指针,即 *(p + i)
还有特别需要注意的地方是,数组名传参时,形参是实参的拷贝,即此时形参是一个指针变量,所以sizeof(形参) = 4或8,计算的是指针变量的大小,所以如果函数体内需要数组大小时,函数声明要加上数组大小的形参。
6. 二级指针
指针变量也是变量,是变量就有地址,那么存放一级指针变量的地址的指针就称为二级指针。
比如:int a = 10, b = 20;
int* pa = &a; //pa是一级指针
int** paa = &pa; //paa是二级指针
*paa = &b; //二级指针解引用找到一级指针pa,即pa = &b
**paa = 30; //即*pa = 30,即b = 30;
多级指针亦是如此。
7. 指针数组
整形数组的主体是数组,存储的数据类型是整形,同理,指针数组的主体还是数组,存储的数据类型是指针。
比如:
char* arr[10]; //操作符 [ ] 的优先级大于 * ,所以arr先和[ ]结合,说明arr是数组,存储char* 指针
int* arr[5]; //int* 指针数组
int** arr[6]; //int**指针数组
进阶
1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'h';
return 0;
}
还有一种使用方式如下:
int main()
{
//字符串”hello word."在常量区,不可修改,const修饰*pstr,取其首字母地址给pstr
const char* pstr = "hello word.";
//pstr可修改
pstr = "How are you?";
//此时ps和*ps都不可修改
char const * const ps = "I am fine!";
return 0;
}
所以,会有如下代码:
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针 指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会 开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
2. 数组指针
是指针,指向数组的指针,存放的是数组的地址的指针变量。
2.1 数组指针的定义
定义示例:int (*p)[5]
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为5个整型的数组。
2.2 &数组名VS数组名
那么,对于数组,比如 int arr[10]; 它的地址是arr还是&arr呢?它俩是一样的吗?
看以下示例结果:
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的意义的是数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。
2.3 数组指针的使用
对于一维数组来说:
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p ,但是我们一般很少这样写代码
return 0;
}
所以数组指针的一个常用地方是:二维数组传参。
类似的,对于二维数组来说,数组名是“首元素”的地址,即第一行元素,就是一维数组的地址,属于数组指针。
示例代码如下:
试着看看:int (*p[10])[5];表示什么。
(解析:p首先和[10]结合,说明p是有10个元素的数组,剩下的 int (*) [5] 就是每个元素的类型,即数组指针,所以p是一个数组指针数组)
3. 数组传参和指针传参
3.1 一维数组传参
3.2 二维数组传参
3.3 一级指针传参
3.4 二级指针传参
4. 函数指针
存储函数地址的指针变量。
定义方式:函数返回类型 (*函数指针名)(函数参数类型列表) = 函数名或&函数名;
如下示例:
上例函数指针的解引用还可以直接:p(1, 2);
再来看看下面的两个解读示例,看看你真的懂了吗:
5. 函数指针数组和指向函数指针数组的指针
示例:
总之,清楚常见操作符的优先级就可以看懂大部分的指针的定义了。
6. 回调函数
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
一个典型的应用就是:C的库函数 qsort
7. 指针和数组相关习题练习详解
如果你要检测一下自己对上述知识掌握如何的话,可点击以下链接到我的Gitee仓库获取指针和数组的练习和笔试习题,包含详解呦!相信小编,肯定会有收获的。
本次分享到这就结束了,如果对你有所帮助,就是对小编最大的鼓励,如果可以的话,点赞,关注加收藏并分享给你好友,一起学习进步吧!
关注小编,持续更新!