一.内存与地址
①何为指针?
内存分为一个个小的内存单元(1byte),每个内存单元都的编号就是地址。在C语言中,地址也称为指针。
②地址总线的介绍
a.地址总线是用来传递地址信息的。
b.32位电脑上有32根地址总线,每根地址总线都有0和1两种状态,那么1根地址总线可以表示两种含义,两根地址总线可以表示4种含义,32根地址总线可以表示2的32次方种含义。每种含义都代表一个地址。
二、指针变量与地址
①取地址操作符
int main()
{
/* 变量创建的本质是向内存申请空间。
向内存申请了4个字节的空间,用来存放20*/
int a = 20;
/* %p是用来打印地址的 */
printf("%p\n", &a);
/* &a表示取出a所占的四个字节中较小的地址(低地址) */
return 0;
}
②得到地址后,该如何存储地址呢?(使用指针变量存储地址)
int main()
{
int a = 20;
printf("%p\n", &a);
/*那么a的地址怎么存储呢? 使用指针变量 */
int* ch = &a;
/*取出a所占的四个字节中较小的地址,将它存放到指针变量ch中
ch的类型是int*
其中*表示ch是指针变量, int表示ch指向的变量a是int类型
*/
char b = 'w';
char* r = &b;
/* char *中的*表示r是指针变量,char表示r指向的变量b是char类型 */
return 0;
}
③解引用操作符
对指针变量解引用,就可以通过指针变量中存储的地址,找到指针指向的对象
int main()
{
int a = 20;
int* pa = &a;
*pa = 200; /* 通过pa中a的地址,找到a,将a的值改为200 */
printf("%d\n", a);//200
return 0;
}
④指针变量的大小
指针变量是用来存储地址的,因此存储地址需要多大的空间,指针变量的大小就是多少。
在32位平台下,含有32根地址总线,每根地址总线含有0和1两种状态,把32根地址总线所组成的二进制序列当做一个地址,则每个地址占4个byte(32bit)。因此在32位平台下,指针变量的大小是4byte。
在64位平台下,含有64根地址总线,每根地址总线都有0和1两种状态,把64根地址总线所组成的二进制序列当做一个地址,则每个地址占8个byte(64bit)。因此在64位平台下,指针变量的大小是8byte。
三、指针变量类型的意义
①指针变量的类型决定了对指针解引用的时候,一次能访问几个字节。
int main()
{
/*0x开头的是16进制数*/
int a = 0x11223344;
int* pa = &a;
/*因为pa是int*类型的指针,因此对pa解引用的时候一次能访问4个字节*/
*pa = 0;
/*此时a的值变成了0*/
int b = 0x11223344;
char* pb = &b;
/*因为pb是char*类型的指针,因此对pb解引用的时候,一次只能访问一个字节*/
*pb = 0;
/*此时b的值不是0*/
return 0;
}
②指针变量的类型决定了指针向前或向后走一步走多大距离。
③ void*类型的指针
由于a是int类型,则&a是int * 类型的地址,当char * 类型的指针接收int * 类型的地址时,编译器会报警告。
为了避免出现这种地址类型不兼容的情况,可以使用void * 类型的指针。 void * 类型的指针可以接受任意类型的地址,但无法对void * 类型的指针进行解引用、指针加减操作。
⼀般 void* 类型的指针是使⽤在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据。
四、const修饰指针
①const修饰变量
被const修饰的变量称为常变量,本质上还是变量,但是变量的值不能被直接修改,具有常量的属性,但可以通过变量的地址,对变量进行修改 。
思考:为什么n要被const修饰呢?是希望n的值不能被修改,但此时指针变量p拿到n的地址后,间接的将n的值修改了!这不合理的。我们希望p拿到了n的地址也不能修改n的值,那么接下来该怎么做呢?
②const修饰指针变量
const修饰指针变量,可以放在 * 的左边,也可以放在 * 的右边,意义是不⼀样的。
a. const放在 * 左边
b. const放在 * 右边
c. const同时放在 * 的左右两边
五、指针的运算
①指针加减整数
②指针减指针
前提:两个指针的类型必须一致,且指向同一块内存空间(比如同一个数组)。
指针相减的绝对值是两个指针之间元素的个数。例如两个int * 的指针相减的绝对值,就是两个指针之间int类型的元素个数;两个char * 的指针相减的绝对值,就是两个指针之间char类型的元素个数。
③指针的关系运算
六、野指针
①野指针的特点?
野指针中存放的地址是随机的(不确定的)。
②野指针成因
a.指针变量作为局部变量时,未初始化。
b.指针变量越界访问
c.指针指向的空间释放
补充:NULL是C语言中定义的标识符常量,它的值为0,0也是地址,只不过这个地址无法访问。(空指针是无法解引用的)
③如何规避野指针
a.指针变量初始化。
当知道指针该指向哪里时,就给它赋值对应的地址;当不知道指针该指向哪里时,就赋值为NULL。
b.小心指针越界
一个程序向内存申请了哪些空间,通过指针也就只能访问这些空间,若访问了其他的空间,就形成了非法访问。
程序的返回值不是0,而是除0之外的任何随机值(比如这里的 -1073741819)表示程序崩溃了。
c.当指针不再使用时,及时赋值为NULL
d.使用指针前检查指针是否为空指针
七、assert的介绍
①何为宏?
#define定义的标识符就是宏,宏是一种预处理指令。宏只是进行单纯的替换,并不参与计算。
#include<stdio.h>
#define m printf
int main()
{
int a = 1;
m("%d\n", a);
return 0;
}
②assert()也属于宏,这个宏常常被称为“断⾔”。使用assert()前要包含头文件<assert.h>
③宏assert( )的作用
assert()接收一个表达式作为参数,如果表达式的结果为真(返回值为非0),则程序正常运行;如果表达式的结果为假,则程序会报错,并给出报错的具体位置。
④assert的优点
可以开启或关闭assert( )。当确定程序没有问题时,就不需要用assert( )进行检查。此时只需在#include前加上#define NDEBUG,就可以关闭assert( )。
⑤assert的缺点
因为引入了额外的检查,增加了程序的运⾏时间。
⑥宏assert()一般是在Debug版本中使用,在release版本中选择禁用掉就行。在VS编译器中的release版本中,assert( )直接就是优化掉了,不起作用。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响用户使用时程序的效率。
八、指针的使用与传值调用
①strlen函数的模拟实现
②函数的传值调用
写一个函数交换两个整型变量的值