网站设计有哪些,建设网站时候应该注意哪些,线下推广小组为了推广开放文明环境地图,wordpress链接数据库文件目录
一、内存和地址
1.1内存
1.2 深入理解计算机编址 二、指针变量和地址
2.1 取地址操作符#xff08;#xff09;
2.2 指针变量和解应用操作符
2.2.1 指针变量
2.2.2 解引用操作符
2.3指针变量的大小
三、指针变量类型的意义 3.1 指针的解引用
3.1指针-整数…
目录
一、内存和地址
1.1内存
1.2 深入理解计算机编址 二、指针变量和地址
2.1 取地址操作符
2.2 指针变量和解应用操作符
2.2.1 指针变量
2.2.2 解引用操作符
2.3指针变量的大小
三、指针变量类型的意义 3.1 指针的解引用
3.1指针-整数
3.3void*无具体类型的指针
四、const修饰变量 4.1 const修饰指针
4.2 const修饰指针变量 五、指针运算
5.1 指针-整数 运算
5.2 指针-指针
5.3 指针的关系运算
六、野指针
6.1.野指针产生原因
6.1.1 指针未初始化
6.1.2 指针越界访问编辑
6.1.3 指针指向的空间释放
6.2 如何规避野指针
6.2.1 指针初始化
6.2.2 避免越界访问
6.2.3 当指针不再使用时即使置NULL指针使用前检查有效性
6.2.4 避免返回局部变量的地址
七、assert断言
八、理解传值调用和传址调用 一、内存和地址
1.1内存
只要讲指针就离不开内存
因为指针就是访问内存的 计算上CPU中央处理器在处理数据的时候需要的数据是在内存中读取的处理后的数 据也会放回内存中那我们买电脑的时候电脑上内存是8GB/16GB/32GB等那这些内存空间如何高效的管理呢 其实也是把内存划分为⼀个个的内存单元每个内存单元的大小取1个字节
计算机常见单位
bit - ⽐特位
byte - 字节
KB
MB
GB
TB
PB1byte 8bit
1KB 1024byte
1MB 1024KB
1GB 1024MB
1TB 1024GB
1PB 1024TB 内存单元编号地址指针 1.2 深入理解计算机编址 CPU访问内存中的某个字节空间必须知道这个字节空间在内存的什么位置⽽因为内存中字节很多所以需要给内存进⾏编址(就如同宿舍很多需要给宿舍编号⼀样)。计算机中的编址并不是把每个字节的地址记录下来⽽是通过硬件设计完成的。
⾸先必须理解计算机内是有很多的硬件单元⽽硬件单元是要互相协同⼯作的。所谓的协同⾄少相互之间要能够进⾏数据传递。但是硬件与硬件之间是互相独⽴的那么如何通信呢答案很简单⽤线连起来。⽽CPU和内存之间也是有⼤量的数据交互的所以两者必须也⽤线连起来。不过我们今天关⼼⼀组线叫做地址总线。
32位机器有32根地址总线 每根线只有两态表⽰0,1【电脉冲有⽆】那么 ⼀根线就能表⽰2种含义2根线就能表⽰4种含义依次类推。32根地址线就能表⽰2^32种含义每⼀种含义都代表⼀个地址。地址信息被下达给内存在内存上就可以找到该地址对应的数据将数据在通过数据总线传⼊CPU内寄存器。
int main()
{
int a 10;//创建变量的本质是向内存申请一块空间为a申请4个字节的空间return 0;
} 对于地址总线、数据总线、控制总线可以这样理解的 控制总线相当于一个控制台传递指令。 数据总线相当于内存数据传输的通道。 地址总线相当于一个内存仓库。 二、指针变量和地址
2.1 取地址操作符
int main()
{int a 10;//a --- 取地址操作符// 单目操作符printf%p\n,a;return 0;
} 对int a10来说创建了四个字节的空间通过调试发现这四个字节都是有地址的当我们通过取地址符来得到a的地址%p是专门用来取地址的占位符时实际上取出的时a所占4个字节中地址较小的字节的地址。虽然整形变量占了4个字节但是只要知道了第1个字节的地址顺藤摸瓜就可以访问到4个字节的数据。
2.2 指针变量和解应用操作符 2.2.1 指针变量 通过2.1我们通过取地址符拿到了地址这个地址是一个数值而将这个数值存储起来方便后期使用就需要我们把地址值存在指针变量里。 int main()
{int a 10;//a --- 取地址操作符// 单目操作符printf%p\n,a;return 0;
}
2.2.2 解引用操作符 通过2.2.1我们学会了怎么将地址保存起来那未来我们也要有方法去取用他就跟我们生活中我们找到一个房间我们希望可以在这个房间里存放或者取走物品同理我们通过了指针变量存储的地址通过地址找到了该地址指向的空间这里就需要用到解引用操作符*来取用空间里数据。
//指针--地址
//指针变量--存放地址的变量int main()
{int a 10;//a --- 取地址操作符// 单目操作符//printf%p\n,a;int* p a;//p是一个变量指针变量是一块空间//编号-地址-指针//int说明p指向对象是int类型的//*在说明p是指针变量
return 0;
}
int main()
{char ch w;char* pc ch;return 0;
}
int main
{int a 10;int * p a;*p 0;//* -解引用操作符间接访问操作符//a 0】//*a 0//a 0
printf(%d,a);//0?return 0;
}
2.3指针变量的大小
32位机器假设有32根地址总线每根地址线出来的电信号转换成数字信号后是1或者0那我们把32根地址线产⽣的2进制序列当做⼀个地址那么⼀个地址就是32个bit位需要4 个字节才能存储。
如果指针变量是⽤来存放地址的那么指针变的⼤⼩就得是4个字节的空间才可以。同理64位器假设有64根地址线⼀个地址就是64个⼆进制位组成的⼆进制序列存储起来就需要8个字节的空间指针变的⼤⼩就是8个字节。
指针的变量大小与类型是无关的只要是指针类型的变量在相同的平台下大小都是一样的32位平台指针大小是4个字节64位平台下指针大小是8个字节 指针变量 - 存放地址的 地址产生地址线上传输的 32根地址线 ——地址是32个0/1组成的二进制序列 要储存这样的地址32bit位的空间 4个字节 int main(){printf(%zd\n, sizeof(char *));printf(%zd\n, sizeof(short *));printf(%zd\n, sizeof(int *));printf(%zd\n, sizeof(double *));return 0;} 32位4 4 4 4 64位8 8 8 8 结论• 32位平台下地址是32个bit位指针变量⼤⼩是4个字节• 64位平台下地址是64个bit位指针变量⼤⼩是8个字节• 注意指针变量的⼤⼩和类型是⽆关的只要指针类型的变量在相同的平台下⼤⼩都是相同的。 三、指针变量类型的意义 指针类型决定了指针进行解应用操作符的时候访问几个字节也就是决定指针的权限。 3.1 指针的解引用
int main()
{ int a 0x11223344;int * pa a;*pa 0;return 0;
} //代码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* 的指针的解引⽤就能访问四个字节。 3.1指针-整数
int main()
{int a 10;int *pa a;char* pc a;printf(pa%p\n,pa);printf(pa1 %p\n,pa1);printf(pc %p\n,pc);printf(pc1 %p\n,pc1);return 0;
} 指针类型决定了指针进行1-1的时候一次走远的距离 int * 1 ---走4个字节整型大小 char* 1---走了1个字节字符大小 3.3void*无具体类型的指针
指针类型
char*:指向字符的指针
short*指向短整型的指针
int*指向整型的指针
float*指向单精度浮点型的指针
........
void*无具体类型的指针这类指针可以用来接受任意类型的地址但是也有局限性就是void*不能直接进行指针的-整数和解引用运算。
#includestdio.h
int main()
{int a 0;char*p a;//int*return 0;
} 上面这个代码我们可以证实这个结论 我们可以把void*想象成一个垃圾桶可以收集任意类型数据的指针但是无法直接去运用解引用和-运算。其实void*的设计可以实现泛型编程的效果使得一个函数可以处理多种类型的数据。
#includestdio.h
int main()
{int a 0;float f 0.0f;void* p a;//int*p f;//float*return 0;
}
#includestdio.h
{int a 10;void* p a;//*p 20;//err//p p 1;//errreutrn 0;
} 四、const修饰变量 4.1 const修饰指针
int main()
{const int a 10;//a 具有了常属性不能被修改了
//a是不是常量虽然a是不能被修改的但是本质上还是变量
//常变量
//
//a 20
//在C中const修饰的变量就是常量
//int arr[a] ;printf(%d\n,a);retrun 0;
}
int main()
{const int a 10;
//a 20;//err
int* p a;
*p 0;
ptintf(a %d\n,a);return 0;
}
4.2 const修饰指针变量 创建指针变量pint*pa之前我们首先要知道3点含义。 1.p内部存放的是a的地址*p可以通过这个地址访问到a。 2.p本身也是变量他有自己的地址。 3.*p是p指向的空间也可以理解成解引用p改变*p其实就是改变a。 int main()
{int a 10;//a--0x0012ff40int b 20;//b--0x0012ff44int * const p a;//0x0012ff40//p b;//err*p 100;//const 修饰指针变量的时候。放*右边//const 限制的是指针变量本身指针变量不能再指向其他变量了//但是可以通过指针变量修改指针变量指向的内容return 0;
}int main()
{int a 10;int b 20;int const * p a;//p b;//OK//*p 100;//err//const 修饰指针变量的时候。放*左边,限制的是指针指向的内容不能通过指针来修改指向的内容//const 限制的是指针变量本身指针变量不能再指向其他变量了//但是可以修改指针变量本身的值修改的指针变量的指向return 0;
}int main()
{int a 10;int b 100;int const * const p a;//p b;//err//*p 0;//errreturn 0;
} const结论: 1.const如果在*左边const修饰的是*p也就是修饰指针指向的内容保证指针指向的内容不能通过指针来改变但是指针变量p本身的内容是可以改变的。 2.const如果在*右边const修饰的是p本身保证指针变量p的内容不能被修改但是指针指向的内容是可以改变的。 3.如果*的两边都有const则const不仅修饰了*p也修饰了p本身所以无论是指针指针指向的内容还是指针变量本身都是不可以被改变的。 五、指针运算
5.1 指针-整数 运算
int main()
{int arr[10] {1,2,3,4,5,6,7,8,9,10};int i 0;int sz sizeof(arr) / sizeof(arr[0])\;for (i 0; i sz;i){printf(%d,arr[i]);}return 0;
}int main()
{int arr[10];int i 0;for (i 0; i 10; i){arr[i] i 1;//数组内10个元素分别为1 2 3 4 5 6 7 8 9 10}//通过指针来访问并打印这个数组int sz sizeof(arr) / sizeof(arr[0]);//sz为数组元素个数//我们需要知道arr的首地址再通过-运算顺藤摸瓜找到后面所有元素int* p arr;//数组名代表数组首元素的地址for (i 0; i sz; i)//如果我想访问1-10{printf(%d , *p);p;} //如果想访问1 3 5 7 9则改成p2即可return 0;
}
5.2 指针-指针
通过5.1我们知道指针整数指针。所以指针-指针得到的是一个整数。 可以模拟实现strlen函数来观察指针的减法strlen函数本质是字符串/0前面出现的元素个数其实strlen函数传入的是字串串首元素的地址如何通过该地址顺藤摸瓜地寻找后面的元素知道遇到/0。
int my_strlen(char* s)
{char* p s;while (*p ! \0)//这里也可以写成*p因为\0的ascii值是0p;//p加1一次就往后移动4个字节return p - s;//指针-指针得到的绝对值是指针之间的元素个数前提条件两个指针指向同一块空间。
}
int main()
{int ret my_strlen(abc);printf(%d, ret);return 0;
} 指针-指针得到的是一个整数而这个整数其实就是指针与指针之间的元素个数但是有个前提条件就是两个指针必须指向同一块空间比如arr[0]-crr[1]就不行。 5.3 指针的关系运算 指针的关系运算就是指针比较大小可以通过运用该知识来访问数组。
int main()
{int arr[10];int i 0;for (i 0; i 10; i){arr[i] i 1;//数组内10个元素分别为1 2 3 4 5 6 7 8 9 10}//通过指针来访问并打印这个数组int sz sizeof(arr) / sizeof(arr[0]);//sz为数组元素个数//我们需要知道arr的首地址再通过-运算顺藤摸瓜找到后面所有元素int* p arr;//数组名代表数组首元素的地址while (p arr sz){printf(%d , *p);p;}return 0;
} p接收的是arr的首地址而sz是元素个数所以通过pp会无限接近arr最后一个元素arr[sz-1]直到打印出来之后while循环结束。 六、野指针
概念野指针就是指针指向的位置是不可知的
6.1.野指针产生原因
6.1.1 指针未初始化 未初始化的变量int *p变量的值是随机的无法访问此时写*p20会报错
6.1.2 指针越界访问
将for循环中的i10改成i20,此时出现越界访问。 当指针指向的返回超出数组的范围就是越界访问此时p是野指针。
6.1.3 指针指向的空间释放 上面这段代码中调用test函数test函数的返回值是一个局部变量test运行后已经被释放了但是第一张图运行还是可以运行出10这个数据原因是我们理解的销毁其实时空间所有权被释放当其他函数执行需要开栈帧时会把这里给占用但是第一张图运行时还没有函数来占用所以10这个数据被保存了下来而第二张图在调用test函数后面又加了一段打印hehe的代码此时printf的调用占用了这块空间此时再去访问得到的就是一个随机值。 当指针指向的空间已经被释放常见的就是调用的函数的返回值是一个局部变量函数一调用结束该变量立刻被销毁。p指向一块无法访问的内容此时p是野指针。
6.2 如何规避野指针 6.2.1 指针初始化 在指针变量创建的时候就要进行初始化如果不知道指针应该指向哪里那么可以将指针赋值给NULLNULL是C函数中定义的一个标识符常量他的值是0地址也是0所以读取该地址时程序会报错相当于程序会提醒你这是个野指针不要去使用。
6.2.2 避免越界访问
比如程序向内存申请了一个存放arr数组的空间那么指针也只能访问这些空间一定不要超出这个范围去访问。
6.2.3 当指针不再使用时即使置NULL指针使用前检查有效性 当我们后期不需要使用这个指针去访问空间时即使内置NULL因为将指针变量设置成NULL一旦误用后系统就会报错这样可以把野指针暂时管理起来。 另一方面当我们书写了大量代码后可能会没有及时发现野指针的出现这时候我们可以在使用前判断是否是NULL根据情况决定是否继续使用这个指针
6.2.4 避免返回局部变量的地址
局部变量在函数执行完空间所有权就会被释放一但其他函数执行需要开栈帧就会占用该空间。 七、assert断言
assert.h 头⽂件定义了宏 assert() ⽤于在运⾏时确保程序符合指定条件如果不符合就报 错终⽌运⾏。这个宏常常被称为“断⾔”。 assert() 宏接受⼀个表达式作为参数。如果该表达式为真返回值⾮零 assert() 不会产⽣ 任何作⽤程序继续运⾏。如果该表达式为假返回值为零 assert() 就会报错在标准错误 流 stderr 中写⼊⼀条错误信息显⽰没有通过的表达式以及包含这个表达式的⽂件名和⾏号。 assert() 的好处 1.⾃动标识⽂件和 出问题的⾏号 2.⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题不需要再做断⾔就在 #include 语句的前⾯定义⼀个宏 NDEBUG (#define NDEBUG)。 assert() 的坏处 1.因为引入了额外的检查增加了程序的运行时间。 2.release版本中需要确保代码没问题的情况下禁用assert操作确保影响用户使用程序的效率。一些编译器的release需要禁用但是vs这样的集成开发环境直接就是优化掉了 八、理解传值调用和传址调用 传值调用和传址调用本质区别就是有无用到指针指针-指针运算模拟strlen函数的实现其实就是传址调用的一种方法其实有一些问题的解决不使用指针是无法解决的比方说下面模拟swap函数的实现。 swap函数即通过这个函数交换两个整型变量的值。在没学习指针前我会这样写---- 但是没有产生我们想要的效果原因是实参传递给形参时形参会单独创建一份临时空间来接受实参对形参的修改不会影响到实参的值,x和y确实接收到了a和b的值不过x的地址和a不一样y的地址和b不一样所以在swap函数内部去交换x和y的值本质上不会影响到a和b说明swap函数是失败的这种函数调用方法在学习函数的时候就已经了解了就是传值调用其特点就是对形参的改变不会影响实参的数据。 所以我们想要实现swap函数就需要使用传址调用让swap函数可以通过地址间接操作main函数中的a和b达到交换的效果。
void swap2(int* px, int* py)
{int temp *px;*px *py;*py temp;
}
int main()
{int a 10;int b 20;printf(交换前a%d b%d\n, a, b);swap2(a, b);printf(交换前a%d b%d\n, a, b);
} 通过传址调用swap函数成功了我们可以总结出以下结论传址调用可以让函数和主调函数之间建立真正的联系在函数内部可以修改主调函数中的变量所以未来我们仅仅只是需要主调函数中的变量值来进行计算而不改变变量值那么可以采用传值调用如果函数内部要修改主调函数中变量的值那么就需要传址调用。