C++ 指针
一. 指针基本概念
指针是另外一种类型的复合类型,复合类型是指基于其它类型定义的类型
(意思是说指针是指向什么类型,那么指针本身就是什么类型)
指针其实就是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量
内存是一个很大的,线性的字节数组。每一个字节都是固定的大小,由8个二进制位组成。最关键的是,每一个字节都有一个唯一的编号,编号从0开始,一直到最后一个字节,这个编号就是所谓的 地址。
指针的值(虚拟地址值)使用一个机器字长
来存储,也就是说,对于一个机器字长为w位的电脑而言,它的虚拟地址空间是0~[2的w次幂] - 1,程序最多能访问2的w次幂个字节。这就是为什么xp这种32位系统最大支持4GB内存的原因了。
机器字长 是指计算机进行一次整数运算所能处理的二进制数据的位数(整数运算即定点整数运算,常见的有8位、16位、32位、64位等)
二. 变量指针内存 三者之间的关系
1. 变量在内存中的存储
举个例子: int a = 1;
因为 int 类型在 32 位操作系统中占 4 个字节,那么 内存中 地址 0028FF40到0028FF43之间4个字节 就存储了变量a ,但是 指针只存储了首地址值 0028FF40 作为 变量 a 的指针地址,所以占据内存的地址就是地址值最小的那个字节的地址
2. 指针在内存中的存储
再举个例子:
int a = 1;
// 声明一个指针 格式 : 变量类型 * 指针变量名 = &变量名
// &变量名 表示获取变量在内存中的首地址值
// * 表示是一个指针
int * p = &a;
p++;
指针也是一种数据类型,在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长,所以无论是什么类型的指针,在32为系统中 指针都是占用4个字节。
在上例中,指针p 的类型是int *,它指向的类型是int,它被初始化为指向整形变量a,接下来的第3句中,指针p 被加了1,编译器是这样处理的:它把指针p的值加上了sizeof(int)大小的字节,在32位程序中,是被加上了4。由于地址是用字节做单位的,故p所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
这个时候可以打印一下 p 指向的的内存地址数据
int main()
{
int a = 1;
// 声明一个指针 格式 : 变量类型 * 指针变量名 = &变量名
// &变量名 表示获取变量在内存中的首地址值
// * 表示是一个指针
int * p = &a;
cout << *p << endl;
p++;
cout << *p << endl;
system("pause");
return 0;
}
运行结果:
第一次打印正确值,因为第一次打印的时候,指针p指向的是变量a的地址值
第二次打印了一个负数,因为 p++ 操作是改变指向的内存地址,而不是把原来的4字节扩张为8字节
三. 指针使用
1. 定义指针对象
定义指针变量时,在变量名前写一个 * 星号,这个变量就变成了对应变量类型的指针变量。必要时要加( ) 来避免优先级的问题:
int * p_int; //指向int类型变量的指针
double * p_double; //指向double类型变量的指针
Person * p_struct; //类或结构体类型的指针
int** p_pointer; //指向 一个整形变量指针的指针
int(*p_arr)[3]; //指向含有3个int元素的数组的指针
int(*p_func)(int, int); //指向返回类型为int,有2个int形参的函数的指针
2. 获取指针指向的数据
int a = 1;
int * p = &a;
printf("%p\n", &a);// 变量a的所在的内存地址
printf("%p\n", p);// 指针p的值 就是a的地址
printf("%d\n", *p);//内存地址为 指针p的值 的数据 输出:1
printf("%p\n", &p);//指针p的所在的内存地址
3. 指针值的状态
- 指向一个对象的地址 例:int * p = a;
- 指向紧邻对象所占空间的下一个位置 例:p++;
- 空指针,意味着指针没有指向任何对象 例:int * p;
- 无效指针(野指针) 例:int * p = 0x00001;
4. 指针的算数运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,指针的运算是有单位的
如上面 2.2 介绍 p++ 的例子:
指针p的类型是int *,它指向的类型是 int,p加1,编译器在1的后面乘上了单位sizeof(int)
若p+3,则编译之后的地址应该是 p的地址加 3 * sizeof(int)
指针运算最终会变为内存地址的元素,内存又是一个连续空间,所以按理只要没有超出内存限制就可以一直增加。这样前面所说的指针值的状态第二条就很好解释了。
四. 函数和指针
4.1 函数的参数和指针
实参传递给形参,是按值传递
的,也就是说,函数中的形参是实参的拷贝份
,形参和实参只是在值上面一样,而不是同一个内存数据对象。这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果
void change(int a)
{
a++;
}
int main(void)
{
int a = 1;
change(a);
cout << a << endl; // a = 1
return 0;
}
有时候我们可以使用函数的返回值来回传数据,在简单的情况下是可以的,但是如果返回值有其它用途(例如返回函数的执行状态量),或者要回传的数据不止一个,返回值就解决不了了。
传递变量的指针可以轻松解决上述问题:
void change(int * a)
{
(*a)++;
}
int main(void)
{
int a = 1;
change(&a);
cout << a << endl; // a = 2
return 0;
}
有时我们会传递类或者结构体对象,而类或者结构体占用的内存有时会比较大,通过值传递的方式会拷贝完整的数据,降低程序的效率。而指针只是固定大小的空间,效率比较高
五. const 和指针
5.1 指向常量的指针
int a = 1;
int b = 2;
// 指向常量的指针,它指向的值不能修改,但是执行可以变
const int * p = &a;
//将const放在类型前面或者后面,二者的意义完全相同
//int const * p;
// *p = b; // 编译报错
p = &b; // 通过
b = 3; // 通过
cout << *p << endl; // 输出:3
5.2 常量指针
int a = 1;
int b = 2;
int * const p = &a;
// p = &b; // 编译报错
*p = 3;
cout << *p << endl;