**#在C语言中 两大关卡就是👻指针和😵结构体
虽然这两个比较难 但是深入理解之后,会发现真的很有意思
刚学C语言的时候 指针对我来说还是一个 很模糊的概念
目录
1️⃣指针是什么
⏩这里我们说 一个内存单元(相当于门牌号🚪)就是一个地址
指针变量的存储方式
int a=1;定义一个int类型变量
int* pa=&a;//取出a的地址,并把a的地址给指针变量pa 这里的pa就是一个指针
⏩由于a是整型 所以a占内存中的四个内存单元
⏩但是a的地址 却只有一个字节的内容
⏩这是因为 指针变量只会存放第一个字节的地址 由于内存是连续存放的 所以只需要知道首字节地址 就可以访问后面的地址啦
指针的大小
指针变量的大小是取决于 计算机的内存的
而计算机的是和他的地址线有关的 例如8根地址线
那么地址空间的命名就是 00000000~11111111 也就是256个地址单元
而这一个地址大小就需要一个字节来存储
⏩总结:
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址
2️⃣指针和指针类型
int*//指向一个整型的指针
char*//指向一个字符型的指针
short*//指向一个短整型的指针
long*//指向长整型的指针
float*//指向单精度浮点型的指针
double*//指向双精度浮点型的指针
指针加减整数
🔰我们知道 指针指向的是存储在内存中的一块空间
所以 那么指针 +1 就是跳过当前对象 指向下一个同类型的对象
如果 是一个int*类型的指针 那么指针+1就是向后走 4个字节
int a=10;
int* pa=&a+1;
可以看到 pa比&a多了四个字节–说明向后访问了四个字节
😄总结:指针的类型决定了指针向前或者向后走一步有多大(距离)
🍴指针的解引用
int main()
{
int a=10;
char* pa=(char*)&a;
int* pa1=&a;
*pa=0;
*pa1=0;
return 0;
}
📌:这是pa的值 可以发现只是改了最低地址(一个字节)的值
✂️这是pa1的值 可以发现 所有的地址都变成了 0 (四个字节都被改了)
⏩总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
3️⃣野指针
⚪️概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的成因
1. 指针未初始化
int main()
{
int* p;
return 0;
}
可以看到 如果没有初始化局部指针变量p 那么其值为 0xcccccccc(随机值)
2. 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
int* test(){
int a=10;
return &a;
}
int main()
{
int *p=test();//调用test函数 把test的返回值赋值给p
return 0;
}
⚠️当test函数调用完 给test函数所开辟的空间就会销毁 也就是说这一片区域已经不为我们所用了 此时p中仍然保存着那一片区域中的某地址 所以这就成了野指针(我们无权限访问)
📏如何规避野指针
1. 指针初始化
// 定义指针的时候进行初始
int* p=NULL;
2. 小心指针越界
例如:对数组进行操作的时候 注意数组下标的范围 不要越界访问
3. 指针指向的空间释放 要置空(NULL)
以上面的 代码为例
int* test(){
int a=10;
return &a;
}
int main()
{
int *p=test();//调用test函数 把test的返回值赋值给p
p=NULL;
return 0;
}
把指针p 置为空
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
✒️ 利用if语句进行有效性判断
int a=10;
int* pa=&a;
if(pa!=NULL)//如果p不为空 才可以使用!
{
*p=20;
}
✏️ assert(断言)的用法
assert()函数可以用来断言某句话是正确的
用来确保某句话是正确的,如果正确就会自动提示 省了编译器的力气
注意包含assert.h头文件
int* my_strlen(char* dest,const char* src)
{
assert(src!=NULL);//断言src不为空
assert(src);//两种方法!
}
如图 我们断言pa不为空
这就是编译器提醒你 pa!=null 这个断言是错误的
4️⃣指针运算
指针+ - 整型
上面我们已经提到 指针±整型 就是向前或者向后访问元素
指针 - 指针
指针减指针 得到的就是两个指针之间元素的个数
end-start 等于start 与 end之间的元素个数 如图即11个元素
指针的关系运算
举一个例子:
定义一个元素个数为五个的整形数组 现在想把所有元素改为0
第一种方法,从最后一个元素后面的指针开始向前
int main(){
int arr[5]={1,2,3,4,5};
int *p=arr;//把数组首元素地址赋值给指针变量p
for(p=&arr[4];p>=&arr[0];){
*--p=0;// 先执行p-- 然后解引用
}
return 0;
}
指针从最后一个元素的后面为起始位置
第一个元素的前面为起始位置 依次把元素改为0
第二种方法,从最后一个元素处为起始位置 开始 最后一次判断 指向了首元素前面的位置
int main(){
int arr[5]={1,2,3,4,5};
int *p=arr;//把数组首元素地址赋值给指针变量p
for(p=&arr[4];p>=&arr[0];vp--)//把 vp的变化 放在了for循环里面
{
*vp=0;
}
return 0;
}
如图所示:
第二种方法也可以实现把这五个元素改为0 但是会存在一定的问题
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,
但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
因为指针指向的是 所指内存空间的起始位置
😄对此 我的理解是
5️⃣ 指针和数组
我们知道 数组名arr有两种含义
1.当出现 &arr 或者 sizeof(arr)的时候 arr表示整个数组
2.其他情况 arr一律指的是数组首元素的地址
并且对于多字节内容 指针指向的是其首字节的地址
那么我们来打印一下
可以发现 三者打印结果是一样的
➡️所以: 可以用一个指针接收数组名 从而利用指针访问数组
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;// p存放的是数组首元素的地址
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//数组元素个数
for (i = 0; i < sz; i++)
{
printf("%p ", p + i);// 利用指针 打印每一个元素的地址
printf("%p ",&arr[i]);// 两种方式效果一样
}
p+i 就是数组下标为i的地址
同理 如果想打印数组的元素 利用指针就可以了
只需要解引用一下就ok
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;// p存放的是数组首元素的地址
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//数组元素个数
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));// 利用指针 访问每一个元素
}
6️⃣二级指针
⌚️什么是二级指针?
📐关于二级指针的运算
int a=10;
int b=20;
int* pa=&a;
int**ppa=&pa;
*ppa=&b;//---->对ppa解引用实际上就等于 pa=&b
**ppa=30;
//等价于 *pa=30;
//等价于 a=30;
7️⃣ 指针数组
😠指针就指针呗 指针数组什么鬼?
那么就不难想到 指针数组 就是数组元素类型是指针的数组
int *arr[5];// 这就是一个指针数组 数组元素个数为5 数组元素类型是 int* 也就是指向整形的指针变量
如图 arr是一个数组,有五个元素,每一个元素是一个整形指针