1. 指针
- 地址
- 生活中的地址:
1.1 指针基础
1.1.1 指针概念
在计算机中所有数据都存储在内存单元中,而每个内存单元都有一个对应的地址,只要通过这个地址就能找到对应单元中存储的数据。
- 在变量名前加&就可以看到其在内存中的地址了
#include <iostream>
int main(void)
{
//对于我们创建类型的变量来说,在变量名前加&就可以看到其在内存中的地址了
int a = 0;
std::cout << "&a = " << &a << std::endl;
int c = 1;
//对于两个普通变量来说,他们在计算机中的地址是随机分配的
int b = 0;
std::cout << "&b = " << &b << std::endl;
//对于数组来说,他们在内存中的排列是连续的
int arr[5] = { 0 };
for (int i = 0; i < 5; i++)
{
std::cout << "&arr[" << i << "] = " << &arr[i] << std::endl;
}
return 0;
}1.1.2 指针变量
在C++中,允许用一个变量来存放其它变量的地址,这种专门用于存储其它变量地址的变量,我们称之为指针变量。简称指针。
int age; // 定义一个普通变量
age = 10;
int *pnAge; // 定义一个指针变量
pnAge = &age;1.1.2.1 指针变量的定义
- 定义:
数据类型 * 指针变量名
例如:int *p;
1.1.2.2 指针变量的初始化
C语言中提供了取地址运算符&来表示变量的地址。其一般形式为:&变量名;
指针变量初始化的方法有两种:定义的同时进行初始化和先定义后初始化。
- 定义的同时进行初始化:cpp
int a = 1; int *pa = &a; - 先定义再初始化:cpp
int a = 5; int *pa, *pb; pa = &a;
注意:
- 多个指针变量可以指向同一个地址。
- 指针的指向是可以改变的。
- 指针的类型要和变量的类型一致。
int a = 1;
int b = 2;
int *pa = &a;
int *pb = &a; // 多个指针变量可以指向同一个地址
pb = &b; // 指针的指向是可以改变的
// double *p_a = &a; // error,类型不一致1.1.2.3 野指针和空指针
指针没有初始化里面是一个垃圾值,称为野指针。程序里不可以出现野指针。
int *p; // 野指针,不推荐把指针初始化为NULL,即为空指针:
int *p = NULL; // 推荐
int *q = 0; // 同样表示空指针1.1.3 指针的大小
- 无论什么类型的指针,得到的大小是:4或8
- 在32位平台,所有的指针(地址)都是32位(4字节)
- 在64位平台,所有的指针(地址)都是64位(8字节)
char ch = 'b';
int *a = NULL;
char *b = &ch;
float *c;
std::cout << "sizeof(a) = " << sizeof(a) << std::endl;
std::cout << "sizeof(b) = " << sizeof(b) << std::endl;
std::cout << "sizeof(c) = " << sizeof(c) << std::endl;1.1.4 通过指针修改变量的值
- 访问指针指向的存储空间:
C++中提供了*来定义指针变量和访问指针变量指向的内存存储空间。- 在定义变量的时候,
*是一个类型说明符,说明定义的这个变量是一个指针变量。 - 定义完指针变量后,再在指针变量前加
*,表示对指针变量解引用,即获取指针变量所指向区域的内容。
- 在定义变量的时候,
int a = 5;
int *p = &a; // 此处的*表示p为一个指针
std::cout << "*p = " << *p << std::endl; // 此处的*表示访问指针指向的存储空间(解引用)- 通过指针变量修改指针指向的存储空间:
int a = 1;
int *pa = &a;
*pa = 3; // 通过指针修改变量的值
std::cout << "*pa = " << *pa << std::endl;
std::cout << "a = " << a << std::endl;- 野指针和空指针不能解引用(因为没有绑定内存),但是可以重新绑定内存再解引用:
int *p; // 野指针
// *p = 1; // 错误,野指针不能解引用
int a = 1;
p = &a; // 重新绑定
std::cout << *p << std::endl; // 正确
int *p1 = NULL;
// *p1 = 1; // 错误,空指针不能解引用
p1 = &a;
std::cout << *p1 << std::endl; // 正确练习:指针方式交换两个变量的值
#include <iostream>
int main()
{
int a = 1;
int b = 2;
int tmp = 0;
int* p_a = &a;
int* p_b = &b;
int* p_tmp = &tmp;
*p_tmp = *p_a;
*p_a = *p_b;
*p_b = *p_tmp;
std::cout << "a = " << a << ", b = " << b << std::endl;
std::cout << "*p_a = " << *p_a << ", *p_b = " << *p_b << std::endl;
return 0;
}1.1.5 const修饰的指针变量
声明指针变量的时候可以使用const关键字。
常量指针:声明指针变量的时候可以把const关键字写在类型名称前,不可以通过这种指针对捆绑存储区做赋值,但是可以对这种指针本身做赋值(不可以通过指针对存储区赋值,但可以对指针的地址赋值)。
cppint a = 1; int b = 2; const int *pa = &a; // 与 int const *pa = &a; 效果一样 // *pa = 3; // error a = 3; // right pa = &b; // right指针常量:声明指针变量的时候可以把const关键字写在指针变量名称前,可以通过这种指针对捆绑存储区做赋值,但是不可以对这种指针本身做赋值(可以通过指针对存储区赋值,但不可以对指针的地址赋值)。
cppint a = 1; int b = 2; int * const pa = &a; *pa = 3; // right // pa = &b; // error
*1.1.6 void 万能指针
void指针可以指向任意变量的内存空间。不应该在无类型指针前直接加*,必须先把无类型指针强制类型转换成有类型指针然后才能使用。
void *p = NULL;
int a = 10;
char c = 'a';
p = &c;
p = &a; // 指向变量时,最好转换为void *
// 使用指针变量指向的内存时,转换为int *
*( (int *)p ) = 11;
std::cout << "a = " << a << std::endl;1.2 指针和数组
数组要通过循环的方式遍历,如果直接打印数组名arr,会发生什么呢?
#include <iostream>
using namespace std;
int main(void)
{
int arr[] = { 1, 2, 3, 4, 5 };
// cout << arr[] << endl; // 错误语法
cout << arr[0] << endl; // 1
cout << arr << endl; // 输出数组首元素的地址
return 0;
}1.2.1 数组指针
1.2.1.1 数组名
数组名字是数组的首元素地址。
#include <iostream>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::cout << "a = " << a << std::endl;
std::cout << "&a[0] = " << &a[0] << std::endl;
return 0;
}1.2.1.2 数组指针操作数组元素
由于数组名可以代表数组首元素地址,数组元素可以通过数组名[下标]访问,那么可以定义一个指针来存放数组的地址,并通过指针[下标]的方式去访问数组元素。
#include <iostream>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int *pa = a;
*pa = 10;
std::cout << "a[0] = " << a[0] << std::endl; // 10
std::cout << "pa[0] = " << pa[0] << std::endl; // 10
return 0;
}1.2.1.3 数组指针加减运算
在指针指向数组元素时,允许以下运算:
- 加一个整数(用+或+=),如p+1
- 减一个整数(用-或-=),如p-1
- 自加运算,如p++, ++p
- 自减运算,如p--, --p
地址加减整数n实际上加减的是n个数组元素的大小(即n * sizeof(元素类型))。
注意:数组名不能参与加减计算(因为数组名是常量指针,不能修改)。
#include <iostream>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7 };
int* pa = a;
double a1[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 };
double* pa1 = a1;
std::cout << "pa = " << pa << std::endl;
std::cout << "pa1 = " << pa1 << std::endl;
std::cout << std::endl;
pa++; // 移动4字节(int大小)
pa1++; // 移动8字节(double大小)
// a++; // error,数组名不能参与加减计算
std::cout << "pa = " << pa << std::endl; // 地址增加4
std::cout << "pa1 = " << pa1 << std::endl; // 地址增加8
std::cout << std::endl;
pa += 2; // 移动8字节(2个int)
// a += 2; // error
std::cout << "pa = " << pa << std::endl;
std::cout << std::endl;
std::cout << "*(pa + 1) = " << *(pa + 1) << std::endl; // 下一个元素
std::cout << "*pa = " << *pa << std::endl; // 当前元素
std::cout << "*(pa - 1) = " << *(pa - 1) << std::endl; // 上一个元素
std::cout << "*pa - 1 = " << *pa - 1 << std::endl; // 当前元素值减1,不是数组元素
return 0;
}通过数组指针遍历数组
#include <iostream>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);
// 方法1:下标法
for (i = 0; i < n; i++)
{
std::cout << a[i] << " ";
}
std::cout << std::endl;
// 方法2:数组名+偏移量
for (i = 0; i < n; i++)
{
std::cout << *(a + i) << " ";
}
std::cout << std::endl;
// 方法3:指针法(指针变量+偏移量)
int* p = a;
for (int i = 0; i < n; i++)
{
std::cout << *(p + i) << " ";
}
std::cout << std::endl;
// 方法4:指针法(指针变量当数组名)
for (int i = 0; i < n; i++)
{
std::cout << p[i] << " ";
}
std::cout << std::endl;
// 方法5:指针移动
for (p = a; p < a + n; p++)
{
std::cout << *p << " ";
}
std::cout << std::endl;
return 0;
}练习:求两个集合的交集
#include<iostream>
#include<cstdlib>
#include<ctime>
int main()
{
srand(time(NULL));
int arr1[10] = { 0 };
int arr2[10] = { 0 };
// 生成arr1并打印
for (int i = 0; i < 10; i++)
{
arr1[i] = rand() % 20;
std::cout << arr1[i] << " ";
}
std::cout << std::endl;
// 生成arr2并打印
for (int i = 0; i < 10; i++)
{
arr2[i] = rand() % 20;
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
// 求交集
std::cout << "两个集合的交集有:" << std::endl;
for (int* p_arr1 = arr1; p_arr1 < arr1 + sizeof(arr1)/sizeof(int); p_arr1++)
{
for (int* p_arr2 = arr2; p_arr2 < arr2 + sizeof(arr2)/sizeof(int); p_arr2++)
{
if (*p_arr2 == *p_arr1)
{
std::cout << *p_arr2 << " ";
break;
}
}
}
return 0;
}1.2.1.4 指针和二维数组
在C++中,多维数组可以通过指针进行访问和操作。
- 多维数组的名称代表数组首元素的地址。对于二维数组
int arr[m][n],arr代表第一行第一个元素的地址(即第一行的地址)。
int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
int *p1 = arr; // error,类型不匹配正确的方式是使用数组指针:
int (*p)[4] = arr; // p指向一个包含4个整数的数组(即一行)二维数组指针加减法
二维数组指针+1其实是加了内部一维数组的长度(即一行的大小)。
#include <iostream>
int main()
{
int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
int (*p)[4] = arr;
std::cout << "arr = " << arr << std::endl;
std::cout << "p = " << p << std::endl;
std::cout << "arr+1 = " << arr+1 << std::endl; // 增加16(一行4个int,4*4=16)
std::cout << "p+1 = " << p+1 << std::endl; // 同样增加16
return 0;
}指针遍历二维数组
可使用数组指针和两个下标的形式来访问,也可使用指针算术遍历。
#include <iostream>
int main()
{
int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
int (*p)[4] = arr;
// 方法1:使用数组指针和下标
for (int i=0; i<3; i++)
{
for (int j=0; j<4; j++)
{
std::cout << p[i][j] << " ";
}
std::cout << std::endl;
}
// 方法2:使用指针算术(将二维数组视为一维)
int *ptr = &arr[0][0];
for (int i=0; i<3*4; i++)
{
std::cout << *ptr << " ";
ptr++;
if ((i+1) % 4 == 0) std::cout << std::endl;
}
return 0;
}1.2.2 指针数组
数组的元素也可以是指针。当数组的元素是指针时,那么这个数组就叫指针数组,指针数组的本质是数组。
*int 类型指针数组
#include <iostream>
int main(void)
{
int arr[5] = {1,2,3,4,5};
int* p_arr[5] = { NULL }; // 指针数组,每个元素都是指针,初始化为NULL
for (int i=0; i<5; i++)
{
p_arr[i] = &arr[i]; // 每个指针元素指向数组arr的对应元素
}
for (int i=0; i<5; i++)
{
std::cout << *p_arr[i] << std::endl;
}
return 0;
}*char 类型指针数组
注意:打印字符地址时,需要转换为void*,否则cout会将其解释为字符串。
#include <iostream>
using namespace std;
int main(void)
{
char p = 'c';
cout << p << endl; // 'c'
cout << (void*)&p << endl; // 地址
char arr[] = "abc";
cout << arr << endl; // "abc"
cout << (void*)arr << endl; // 地址
return 0;
}指针数组存储多个字符串:
#include <iostream>
int main(void)
{
const char arr1[] = "abc";
const char arr2[] = "def";
const char arr3[] = "qwe";
const char arr4[] = "wasd";
const char arr5[] = "xyz";
// 字符串数组:每个元素是指针,指向字符串的首地址
const char* p_arr[5] = { arr1, arr2, arr3, arr4, arr5 };
// 打印每个字符串的地址
for (int i=0; i<5; i++)
{
std::cout << (void*)p_arr[i] << std::endl;
}
// 打印每个字符串
for (int i=0; i<5; i++)
{
std::cout << p_arr[i] << std::endl;
}
// 访问单个字符
for (int i=0; i<4; i++) // 假设第四个字符串"wasd"长度为4
{
std::cout << p_arr[3][i] << std::endl; // 输出w, a, s, d
}
return 0;
}