1 指针
指针是什么?与内存地址的关系是什么?
下图是 4G 内存中每个字节单元和它的编号:
- 每个字节单元可以存储8个二进制位,即1个字节大小的内容;
- 字节单元也被称为内存单元;
- 为了方便寻址,每个内存单元都有唯一的编号,这个编号就是内存地址(Address)或者指针(Address)
- 地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为
0x00000000
,最大的地址为0xFFFFFFFF
。
【答】
- 指针是内存单元的编号;
- 指针变量的值是一个内存地址。用
*
取指针变量的值得到的就是该指针变量指向的内存地址存储的值。
1.1 如何创建指针
#include <stdio.h>
int main() {
int a = 10;
int* p1 = &a;
int *p2 = &a;
int** pp = &p1;
printf("%p
", &a);
printf("%p
", p1);
printf("%p
", p2);
printf("%p
", pp);
system("pause");
return 0;
}
一次执行中,运行结果如下:
我们画图来表示:
-
但在编写代码的过程中,我们认为变量名表示的是数据本身;例如,a 是 int 型,表示的是 4字节的整数。
-
a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;
&a
表示就是该变量的首地址。 -
指针可以认为是一种数据类型,
int*
就可以视为一个整体。p1 就是一个指针,这个指针指向变量 a 的首地址。 -
int* p = &a;
和int *p = &a;
写法略有差异,但是实际功能是相同的。因此,&a
,p1
,p2
表示的是同一个内存单元。 -
int** pp
就等同于(int*)* pp
,pp 是指针,指向一个内存单元,在32位系统中,int*
占4个字节,所以 pp 存储的值等于0x0133F7D4
,这个值又是一个内存地址,或者说指针。
【答】:定义指针用 int* p;
或者 int *p
,个人更推荐前者。
1.2 指针占多少字节
我们先来看一个程序:
#include <stdio.h>
int main() {
char* p1;
short* p2;
int* p3;
long* p4;
float* p5;
double* p6;
printf("%d
", sizeof(p1));
printf("%d
", sizeof(p2));
printf("%d
", sizeof(p3));
printf("%d
", sizeof(p4));
printf("%d
", sizeof(p5));
printf("%d
", sizeof(p6));
system("pause");
return 0;
}
我们来看一下运行结果:
- 我们发现任何类型的指针变量都是占用4个字节。
- 因为我们的程序是用32位编译的,所以总是4个字节
【答】
指针的大小是固定的;指针所占的字节数跟编译的程序位数有关。如果是32位程序,指针的宽度就是4字节,如果是64位程序,指针的宽度就是8字节。
1.3 指针变量的运算
我们再来看一段程序:
#include <stdio.h>
int main()
{
char c = 'C';
short s = 1;
int i = 100;
double d = 99.9;
char* p1 = &c;
short* p2 = &s;
int* p3 = &i;
double* p4 = &d;
printf("size char=%6d short=%5d int=%7d double=%4d
", sizeof(char), sizeof(short), sizeof(int), sizeof(double));
printf("Init p1=%8p p2=%8p p3=%8p p4=%8p
", p1, p2, p3, p4);
// 加法运算
p1++;p2++;p3++;p4++;
printf("p++ p1=%8p p2=%8p p3=%8p p4=%8p
", p1, p2, p3, p4);
// 减法运算
p1-=2;p2-=2;p3-=2;p4-=2;
printf("p-=2 p1=%8p p2=%8p p3=%8p p4=%8p
", p1, p2, p3, p4);
system("pause");
return 0;
}
执行结果如下:
- 自增:p1,p2,p3,p4每次加1,他们的地址分别增加1、2、4、8个字节,这个增量正好等于 char,short,int,double 类型的长度。
- 减法:p1,p2,p3,p4每次减2,他们的地址分别减少2、4、8、16个字节,这个减少量正好等于 char,short,int,double 类型的长度的2倍。
1.4 单目操作符&
和*
首先,定义指针变量void* p
或者 void *p
,以及定义引用变量 int& a
或者 int &a
;在定义变量时,会比 &
和 *
单纯作为单目运算符要多一个类型声明。
首先,作为单目运算符,它的特点就是只有一个操作数:
int a = 1;
int* p = &a;
*p = 10;
- 首先定义了一个变量a
- 接着,定义了指针变量p,并为它赋值。赋值时,用上了单目运算符
&
来取得变量a的首地址,并赋值给指针p。 - 最后,再次使用了单目运算符
*
来获取指针p指向的数据,并修改了数据内容。
&
作为 单目运算符 使用时,是取地址符。
*
作为 单目运算符 使用时,是解引用符。
2 指针常量和常量指针
我之前在学习指针的时候,总是会把指针常量和常量指针混淆起来。最主要的原因是,按照一般的习惯,我们是从左向右读,所以我们会理所当然地认为const int* p
是常量指针。但是实际上你倒过来念就是正确的。
2.1 指针常量
int a = 0;
int b = 1;
// 指针常量
const int* p = &a;
// *p = 10; // 错误,内容不可以修改
p = &b; // 正确,指针可以修改
记忆方法:按照英文反着念 int*
,const
: 指针常量。中文的习惯,最终定性为“常量”,所以指针指向的数据是个常量,不可以被修改。
2.2 常量指针
int a = 0;
int b = 1;
// 常量指针
int* const p = &a;
*p = 10; // 正确,指针指向的数据可以修改
// p = &b; // 错误,指针地址不可以修改
记忆方法:反着念定义关键字 const
,int*
:常量指针。中文的习惯,最终定性为“指针”,常量修饰“指针”一词,所以指针地址不可以被修改。但是,指针指向的数据可以被修改。
2.3 地址和数据都不许改
int a = 0;
int b = 1;
const int* const p = &a;
// *p = 10; // 错误,指针指向的数据不可以修改
// p = &b; // 错误,指针地址不可以修改
3 指针与数组
3.1 访问数组中的元素
int arr[] = {1,2,3,4,5};
int* start = arr;
- 首先,初始化了整型数组 arr;
- 接着,把数组名赋值给了指针start;
函数名、字符串名和数组名表示的是代码块或数据块的首地址
我们想要遍历数组,首先需要计算数组的元素个数:
int len = sizeof(arr) / 4;
然后,不使用指针时,我们访问数组的是这样:
for (int i = 0; i < len; i++) {
printf("%d
", arr[i]);
}
使用指针访问数组是这样的:
for (int i = 0; i < len; i++) {
printf("%d
", *(start+i));
}
3.2 通过char型指针访问short数组会发生什么?
short arr[] = {1, 2, 3};
char* p = reinterpret_cast<char *>(arr);
for (int i = 0; i < 3; i++) {
printf("%d
", *(p+i));
}
运行结果:
4 函数指针与指针函数
4.1 函数指针
函数指针,其本质是一个指针变量,该指针指向某个函数。
#include <stdio.h>
void sayHello();
int main()
{
void (*p)();
p = sayHello;
(*p)(); // 效果等同于调用 sayHello();
p(); // 效果等同于调用 sayHello();
system("pause");
return 0;
}
void sayHello() {
printf("Hello World!");
}
利用函数指针进行调用:
(*p)()
p()
很久很久以前C语言只允许前者,后来大家觉得这么写太麻烦就规定了后者能达到同样效果。
后者在编译时和前者做相同的事情。
类似的语法上的便利还有:
p->h
通过指针访问结构成员,等价于(*p).h
p[n]
通过指针访问数组元素,等价于*(p+n)
4.2 指针函数
指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。形如
int *fun(int x,int y);
4.3 typedef函数指针
改造一下4.1函数指针中的例子:
void sayHello();
// typedef 函数指针
typedef void (*HI)();
HI funHi;
int main()
{
funHi = sayHello;
funHi(); // 效果等同于调用 sayHello();
(*funHi)(); // 效果等同于调用 sayHello();
return 0;
}
void sayHello() {
printf("Hello World!");
}
参考文档
《C语言指针是什么?1分钟彻底理解C语言指针的概念》阅读
《函数名与函数指针(了解)》阅读
《typedef函数指针用法》阅读