一、指针(C语言灵魂)
1.1.指针的定义:指针本质就是一个变量,而这个变量永远只能存储一个内存地址(编号)所以此变量对应的专业术语叫指针变量,通过指针变量保存的地址就可以对这块内存区域任意访问(读查看,写修改),而指针指向的内存区域可以保存一个数字,而这个数字有数据类型
1.2.指针变量定义的语法格式:
a)书写形式1:
int * 变量名;
例如:int * pa; //定义一个指针变量
b)书写形式2:
int* 变量名;
例如:int* pa; //定义一个指针变量
c)书写形式3:
int *pa; //定义一个指针变量
语义:都是定义一个指针变量,将来这个变量pa能够保存一块内存区域的首地址,并且这块内存区域保存着一个int类型的数据,由于指针变量也是变量,同样需要分配内存空间
问:指针变量占用多大的内存空间呢?
答:得看保存的地址有多大,这个跟计算机硬件相关:
32位系统,一个地址值32位,4字节
64位系统,一个地址值64位,8字节
结论:指针变量分配的内存空间为4字节或者8字节,所以指针变量本身没有数据类型,只是它指向的内存区域保存的数字有数据类型,所以int不是给指针变量用,而是给指针变量指向的内存区域保存的数字用的
d)连续定义指针变量形式:
int *pa, *pb; //定义两个指针变量
int *pa, pb; //pa是指针变量,而pb就是一个普通的int类型变量
e)切记:定义指针变量后如果不初始化,此指针变量保存的一个地址值是随机的,也就是此指针变量指向任意内存区域,相当危险,因为此块内存区域,不是操作系统合法给你分配的内存,此指针变量为野指针!
1.3.指针变量初始化通过取地址&来进行:
int a = 250; //分配4字节内存空间,存储250数字,而这个数字类型为int类型
int *pa = &a; //定义一个指针变量,也就是分配一个4字节内存空间(前提是32位)
保存变量a对应的内存空间的首地址,俗称pa指向a
问:一旦通过指针变量来获取到指向的内存区域的首地址,如何通过指针变量,对指向的内存区域进行操作,也就是对指向的内存区域进行读查看或者写修改呢?
答:通过解引用运算符:*
1.4.解引用运算符(又称取目标运算符):*
功能:就是通过指针变量对指向的内存区域进行读查看或者写修改
语法格式:*指针变量 = 取目标
例如:
char a = 100;
char *pa = &a;
或者:
char a = 100;
char *pa = NULL;
pa = &a;
//打印pa指向a的内存数据
printf("%d\n", *pa); //100
//修改pa指向a的内存数据
*pa = 10; //结果是变量a的内存由原来的100变成10
结论:sizeof(指针变量名) = 4(永远的)
1.5.特殊指针:空指针和野指针
a)空指针:空指针变量保存一个空地址,用NULL表示,其实就是编号为0地址
空指针不可以随意访问,否则造成程序的崩溃!
例如:
int *pa = NULL;
printf("pa指向的0地址保存的数据为%#x\n", *pa);
*pa = 250; //向0地址写入数据250
b)野指针:没有初始化的指针变量,它保存着一个随机地址,指向着一块无效的内存,因为这块内存区操作系统并没有给你分配,如果对野指针进行非法访问,也会造成程序的崩溃!
int *pa; //pa就是野指针
printf("pa指向的0地址保存的数据为%#x\n", *pa);
*pa = 250; //向0地址写入数据250
c)切记:实际开发代码的编程规范
如果定义一个指针变量,一开始不清楚它到底指向谁,千万不能不初始化,否则变成了野指针所以此时要求初始化为空指针NULL,一旦初始化为NULL,将来程序后面使用时,要记得对指针变量进行安全的判断,判断它是否为NULL,如果为NULL,让程序结束或者函数返回,如果为有效地址,程序才能继续通过指针变量进行操作
例如:
int *pa; //不建议这么写,危险
//安全做法:
int *pa = NULL; //赋值为空指针
if(NULL == pa) {
printf("pa指向空指针,不能继续访问.\n");
return -1 或者exit(0); //函数返回或者程序退出
} else {
printf("pa指向一块有效内存,可以继续访问\n");
printf("%d\n", *pa);
*pa = 250;
}
//安全做法:
int *pa = NULL; //赋值为空指针
int a = 250;
pa = &a; //让pa指向a
if(NULL == pa) {
printf("pa指向空指针,不能继续访问.\n");
return -1 或者exit(0); //函数返回或者程序退出
} else {
printf("pa指向一块有效内存,可以继续访问\n");
printf("%d\n", *pa);
*pa = 251;
}
1.6.指针运算
a)指针可以和一个整数做加减法运算,简称地址运算
切记:计算结果和指针指向的变量数据类型有关系
b)指针计算公式:
1.char型指针+1,表示实际地址+1
例如:
char *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1001
2.short型指针+1,表示实际地址+2
short *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1002
3.int/long型指针+1,表示实际地址+4
long *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1004
1.7.指针和数组的那点事儿(之前都是研究指针和变量的那点事儿)
a)回顾数组相关内容
定义数组:int a[4] = {'A', 'B', 'C', 'D'};
结论:
1.数组名就是数组的首地址,同样遵循指针的运算公式
2.&a[2]就是第2个元素的首地址
3.a+2也是第2个元素的首地址
4.&a[2] - a = a + 2 - a = 2个元素,表示第2个元素和第0个元素之间差2个元素,实际的地址差8个字节=2个元素*int
5.目标:a,&a[2],a+2都是地址,干脆定义一个指针变量保存数组的首地址,将来利用指针变量和指针变量的计算公式很轻松即可访问元素
b)指针和数组关系公式:
int a[4] = {1,2,3,4,5};
int *pa = a; //定义指针变量保存整个数组的首地址
1.指针和数组的写法1:
//查看所有元素的值:
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
printf("%d\n", *(p+i));
//将所有元素乘10倍
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
*(p+i) *= 10;
2.指针和数组的写法2:
int len = sizeof(a)/sizeof(a[0]) ; //求数组的内存空间大小
//查看所有元素的值:
for(pa = a; pa < a + len; p++)
printf("%d\n", *p);
//将所有元素乘10倍
for(pa = a; pa < a + len; )
*p++ *= 10; //*p++:先算*p,后做p++
3.指针和数组的写法3:
//查看所有元素的值:
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
printf("%d\n", p[i]);
//将所有元素乘10倍
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
p[i] *= 10;
切记:"[]"运算符要经过两步运算:
例如:a[2]经过两步运算:
1.先算地址:a + 2
2.再取值:*(a+2)
所以:p[2]经过两步运算;
1.先算地址:p+2
2.再取值:*(p+2)
c)终极获取元素值的公式: a[i] = p[i] = *(a+i) = *(p+i)
1.8.常量,常量指针,指针常量,常量指针常量:围绕关键const(笔试题必考)
a)常量定义:不可修改的值,例如:250,'A'等
b)const关键字功能:常量化,四种形式:
1.const可以修饰普通变量,一旦修饰该变量就会被当成常量处理,即其值一经初始化再也不能改
例如:
const int a = 250;
a = 200; //gcc编译时会报错
2.常量指针:不能通过指针变量来修改指向的内存区域的值(保护内存区域不可乱改)
例如:
int a = 250;
const int *p = &a; //定义初始化一个常量指针
或者
int const *p = &a; //定义初始化一个常量指针
*p = 200; //gcc编译时会报错
printf("%d\n", *p); //可以,仅仅是读查看
或者
int b = 300;
p = &b; //可以,指针变量p本身是可以修改的,p此时指向b
*p = 400; //gcc编译时会报错
printf("%d\n", *p);//可以,仅仅是读查看
3.指针常量:指针永远指向一块内存区域,不能再指向别的地方(保护指针不可乱指向)
例如:
int a = 100;
int* const p = &a;
*p = 300; //可以,可以修改指向的内存区域
int b = 200;
p = &b; //不可以,gcc报错
4.const int * const p:常量指针常量,表示p本身不可修改,同时p指向的目标也不能修改只能通过p来查看内存区域的值
例如:
int a = 100;
const int* const p = &a;
*p = 300; //不可以,可以修改指向的内存区域
int b = 200;
p = &b; //不可以,gcc报错
printf("%d\n", &p); //可以