我的家乡网站建设模板下载,中英网站建设,生态养殖网站模板,免费编程软件下载个人主页 #xff1a; zxctscl 如有转载请先通知 文章目录 1.内存与地址2.指针变量与地址2.1 取地址操作符2.2 指针变量2.3 指针类型2.4 解引用操作符2.5 指针变量的大小 3. 指针变量类型的意义3.1 指针的解引用 4. const修饰指针4.1 const修饰变量4.2 const修饰指针变量… 个人主页 zxctscl 如有转载请先通知 文章目录 1.内存与地址2.指针变量与地址2.1 取地址操作符2.2 指针变量2.3 指针类型2.4 解引用操作符2.5 指针变量的大小 3. 指针变量类型的意义3.1 指针的解引用 4. const修饰指针4.1 const修饰变量4.2 const修饰指针变量 5. 指针运算5.1 指针-整数5.2 指针-指针5.3 指针的运算关系 6. 野指针6.1 野指针成因6.2 如何规避野指针6.2.1 指针初始化6.2.2 小心指针越界6.2.3 指针变量不再使用时及时置NULL指针使用之前检查有效性 7. assert断言8. 指针的使用和传址调用 1.内存与地址
大家对地址都不陌生就像在生活中住酒店如何找到房间那不就通过房卡上的房间号先确定楼层在确定房间。而这些房间号我们也叫地址。 把内存划分为一个个内存单元一个单元为一个字节而计算机中都是以一个比特位存储一个2进制位一个字节也就是8个比特位。 这使得每个内存单元都有一个编号通过这个编号就能迅速找到这个内存空间。 在C语言中给地址起了新名叫指针
所以我们理解的内存单元的编号 地址 指针
2.指针变量与地址
2.1 取地址操作符
在C语言中创建变量其实就是在向内存申请空间。就像这样
#include stdio.h
int main()
{int a 5;return 0;
}创建了整型变量a内存中就申请了4个字节用来存放整数5。每个字节都有地址而上面申请的4个字节的地址分别在下面划红线四个。 如果我们想得到a的地址就需要用到取地址操作符。 像下面这样就可但值得注意的是我们打印的是地址用到的是**%p**。
#include stdio.h
int main()
{int a 5;a;printf(%p\n, a);return 0;
}2.2 指针变量
我们通过取地址操作符得到的仅仅是地址它就只是一个数值有时候为了方便使用我们把它用指针变量存储。
#include stdio.h
int main()
{int a 5;int* p a;return 0;
}指针变量也是一种变量不过是用来存放地址存放在指针变量中的值被理解为指针。
2.3 指针类型
指针也是有类型的。 就像前面所写的:
int a 5;
int* p a;在p的左边就是int*,*说明的是p为指针变量而前面的int就是说明p指向的是整型int类型的对象。 同理指向char型的指针变量就是char*。
char b a;
char* p b;2.4 解引用操作符
在C语言中我们找到地址就可以对地址所指向的对象而此时所要用到的就是解引用操作符*。
#include stdio.h
int main()
{int a 5;int* p a;*p0;return 0;
}而在上面的代码中原来a为5我们通过指针拿到了a的地址然后通过解引用操作*p0将原来a的5改为0。
2.5 指针变量的大小
32位机器假设有32根地址总线每根地址线出来的电信号转换成数字信号后是1或者0那我们把32根地址线产⽣的2进制序列当做⼀个地址那么⼀个地址就是32个bit位需要4个字节才能存储。 如果指针变量是⽤来存放地址的那么指针变的⼤⼩就得是4个字节的空间才可以。 同理64位机器假设有64根地址线⼀个地址就是64个⼆进制位组成的⼆进制序列存储起来就需要8个字节的空间指针变的⼤⼩就是8个字节。
#include stdio.h
int main()
{printf(%zd\n, sizeof(char *));printf(%zd\n, sizeof(short *));printf(%zd\n, sizeof(int *));printf(%zd\n, sizeof(double *));printf(%zd\n, sizeof(float *));return 0;
}32位平台下地址指针变量大小是4个字节 64位平台下地址指针变量大小是8个字节 结论
32位平台下地址指针变量大小是4个字节64位平台下地址指针变量大小是8个字节注意指针变量的大小和类型是无关的只要指针类型的变量在相同的平台下大小都是相同的。
3. 指针变量类型的意义
指针变量的大小和类型无关只要是指针变量在同一个平台下大小都是一样的为什么还要有各种各样的指针类型呢
3.1 指针的解引用
对比下面2段代码主要在调试时观察内存的变化。
//代码1
#include stdio.h
int main()
{int n 0x11223344;int* pi n;*pi 0;return 0;
}//代码2
#include stdio.h
int main()
{int n 0x11223344;char* pc (char*)n;*pc 0;return 0;
}调试我们可以看到代码1会将n的4个字节全部改为0
但是代码2只是将n的第一个字节改为0。
结论指针的类型决定了对指针解引用的时候有多大的权限一次能操作几个字节。比如 char* 的指针解引用就只能访问一个字节而 int* 的指针的解引用就能访问四个字节。
4. const修饰指针
4.1 const修饰变量
变量是可以修改的如果把变量的地址交给一个指针变量通过指针变量的也可以修改这个变量。 但是如果我们希望一个变量加上一些限制不能被修改怎么做呢这就是const的作用。
#include stdio.h
int main()
{int m 0;m 20;//m是可以修改的const int n 0;n 20;//n是不能被修改的return 0;
}代码中n是不能被修改的其实n本质是变量只不过被const修饰后在语法上加了限制只要我们在代码中对n就行修改就不符合语法规则就报错致使没法直接修改n。
但是如果我们绕过n使用n的地址去修改n就能做到了虽然这样做是在打破语法规则。
#include stdio.h
int main()
{const int n 0;printf(n %d\n, n);int* p n;*p 20;printf(n %d\n, n);return 0;
}可以看到这里一个确实修改了但是我们还是要思考一下为什么n要被const修饰呢就是为了不能被修改如果p拿到n的地址就能修改n这样就打破了const的限制这是不合理的所以应该让p拿到n的地址也不能修改n那接下来怎么做呢
4.2 const修饰指针变量
测试无const修饰的情况
#include stdio.h
//代码1
void test1()
{int n 10;int m 20;int* p n;*p 20;//ok?p m; //ok?
}测试const放在*的左边情况
void test2()
{//代码2int n 10;int m 20;const int* p n;*p 20;//ok?p m; //ok?
}测试const放在*的右边情况
void test3()
{int n 10;int m 20;int* const p n;*p 20; //ok?p m; //ok?
}测试*的左右两边都有const
void test4()
{int n 10;int m 20;int const* const p n;*p 20; //ok?p m; //ok?
}const修饰指针变量的时候
const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。const如果放在*的右边修饰的是指针变量本身保证了指针变量的内容不能修改但是指针指向的内容可以通过指针改变。
5. 指针运算
指针的基本运算有三种分别是 • 指针± 整数 • 指针-指针 • 指针的关系运算
5.1 指针±整数
数组在内存中是连续存储的只要知道第一个元素的地址后面的元素依次就能找到。
int arr[]{1,2,3,4,5};而所对应的下标为01234。
在对不同类型指针变量加减时结果不同 举个例子
#include stdio.h
int main()
{int n 10;int* p1 n;char* p2 n;printf(p1%p\n, p1);printf(p11%p\n, p11);printf(p2%p\n, p2);printf(p21%p\n, p21);return 0;
}在下面为结果 int类型的就跳过了4个字节 char类型就跳过1个字节 结论 指针的类型决定了指针加减整数时一次性跳过多少个字节。
5.2 指针-指针
在指针变量相同类型时计算出的是中间间隔的个数。 举个例子
#include stdio.h
int main()
{int arr[10] { 0 };int* p1 arr[9];int* p2 arr[0];int ret p1-p2 ;printf(%d\n, ret);return 0;
}结果为 指针类型不同时不能进行指针的加减运算。
5.3 指针的运算关系
计算数组的元素个数时我们使用了sizeof(数组名)而sizeof中单独放数组名这里的数组名表示整个数组计算的是整个数组的大小单位是字节。 sizeof(arr[0]计算的是首元素的大小单位也是字节。
#include stdio.h
int main()
{int arr[] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);printf(%d\n, sz);return 0;
}数组名就是数组首元素(第一个元素)的地址是对的但是有两个例外 1.sizeof(数组名)sizeof中单独放数组名这里的数组名表示整个数组计算的是整个数组的大小单位是字节。 2.数组名这里的数组名表示整个数组取出的是整个数组的地址。 除此之外任何地方使用数组名数组名都表示首元素的地址。
6. 野指针
概念 野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的
6.1 野指针成因
指针未初始化
#include stdio.h
int main()
{int* p;//局部变量指针未初始化默认为随机值*p 20;return 0;
}指针越界访问
#include stdio.h
int main()
{int arr[10] { 0 };int* p arr[0];int i 0;for (i 0; i 11; i){//当指针指向的范围超出数组arr的范围时p就是野指针*(p) i;}return 0;
}指针指向的空间释放
#include stdio.h
int* test()
{int n 100;return n;
}
int main()
{int* p test();printf(%d\n, *p);return 0;
}6.2 如何规避野指针
6.2.1 指针初始化
如果明确知道指针指向哪⾥就直接赋值地址如果不知道指针应该指向哪⾥可以给指针赋值NULL.NULL 是C语言中定义的一个标识符常量值是00也是地址这个地址是无法使用的读写该地址会报错。
#ifdef __cplusplus#define NULL 0
#else#define NULL ((void *)0)
#endif初始化如下
#include stdio.h
int main()
{int num 10;int*p1 num;int*p2 NULL;return 0;
}6.2.2 小心指针越界
一个程序向内存申请了哪些空间通过指针也就只能访问哪些空间不能超出范围访问超出了就是越界访问。
6.2.3 指针变量不再使用时及时置NULL指针使用之前检查有效性
当指针变量指向一块区域的时候我们可以通过指针访问该区域后期不再使用这个指针访问空间的时候我们可以把该指针置为NULL。因为约定俗成的一个规则就是只要是NULL指针就不去访问同时使用指针之前可以判断指针是否为NULL。 我们可以把野指针想象成野狗野狗放任不管是⾮常危险的所以我们可以找一棵树把野狗拴起来就相对安全了给指针变量及时赋值为NULL其实就类似把野狗栓前来就是把野指针暂时管理起来。 不过野狗即使拴起来我们也要绕着走不能去挑逗野狗有点危险对于指针也是在使用之前我们也要判断是否为NULL看看是不是被拴起来起来的野狗如果是不能直接使用如果不是我们再去使用。
#include stdio.h
int main()
{int arr[10] { 1,2,3,4,5,67,7,8,9,10 };int* p arr[0];for (i 0; i 10; i){*(p) i;}//此时p已经越界了可以把p置为NULLp NULL;//下次使⽤的时候判断p不为NULL的时候再使⽤//...p arr[0];//重新让p获得地址if (p ! NULL) //判断{//...}return 0;
}7. assert断言
assert.h 头文件定义了宏 assert() 用于在运行时确保程序符合指定条件如果不符合就报 错终止运行。这个宏常常被称为“断言”。
assert(p ! NULL);代码在程序运行到这一行语句时验证变量 p 是否等于 NULL 。如果确实不等于 NULL 程序继续运行否则就会终止运行并且给出报错信息提示。 assert() 宏接受一个表达式作为参数。如果该表达式为真返回值非零 assert() 不会产生任何作用程序继续运行。如果该表达式为假返回值为零 assert() 就会报错在标准错误流 stderr 中写入一条错误信息显示没有通过的表达式以及包含这个表达式的文件名和行号。 assert() 的使用对程序员是非常友好的使用assert() 有几个好处它不仅能自动标识文件和出问题的行号还有一种无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题不需要再做断言就在 #includeassert.h 语句的前面定义一个宏 NDEBUG 。
#define NDEBUG
#include assert.h然后重新编译程序编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题可以移除这条 #define NDBUG 指令或者把它注释掉再次编译这样就重新启⽤了 assert() 语句。
assert() 的缺点是因为引入了额外的检查增加了程序的运行时间。 ⼀般我们可以在debug中使用在release版本中选择禁⽤assert就行在VS这样的集成开发环境中在release版本中直接就是优化掉了。这样在debug版本写有利于程序员排查问题在release版本不影响用户使用时程序的效率。
8. 指针的使用和传址调用
学习指针的目的是使用指针解决问题那什么问题非指针不可呢 例如写一个函数交换两个整型变量的值 一番思考后我们可能写出这样的代码
#include stdio.h
void Swap1(int x, int y)
{int tmp x;x y;y tmp;
}int main()
{int a 0;int b 0;scanf(%d %d, a, b);printf(交换前a%d b%d\n, a, b);Swap1(a, b);printf(交换后a%d b%d\n, a, b);return 0;
}其实没产生交换的效果这是为什么呢 调试一下试试呢 我们发现在main函数内部创建了a和ba的地址是0x00cffdd0b的地址是0x00cffdc4在调用Swap1函数时将a和b传递给了Swap1函数在Swap1函数内部创建了形参x和y接收a和b的值但是x的地址是0x00cffcecy的地址是0x00cffcf0x和y确实接收到了a和b的值不过x的地址和a的地址不一样y的地址和b的地址不一样相当于x和y是独立的空间那么在Swap1函数内部交换x和y的值自然不会影响a和b当Swap1函数调用结束后回到main函数a和b的没法交换。Swap1函数在使用的时候是把变量本身直接传递给了函数这种调用函数的方式我们之前在函数的时候就知道了这种叫传值调用。
结论实参传递给形参的时候形参会单独创建一份临时空间来接收实参对形参的修改不影响实参。所以Swap是失败的了。
那怎么办呢 我们现在要解决的就是当调用Swap函数的时候Swap函数内部操作的就是main函数中的a和b直接将a和b的值交换了。那么就可以使用指针了在main函数中将a和b的地址传递给Swap函数Swap函数里边通过地址间接的操作main函数中的a和b就好了
#include stdio.hvoid Swap2(int* px, int* py)
{int tmp 0;tmp *px;*px *py;*py tmp;
}
int main()
{int a 0;int b 0;scanf(%d %d, a, b);printf(交换前a%d b%d\n, a, b);Swap2(a, b);printf(交换后a%d b%d\n, a, b);return 0;
}有问题请指出大家一起进步