Section 14:指针
先上runoob的典中典:《学习C++的指针既简单又有趣》 😅
指针是一个变量,其值为另一个变量的内存地址,其占用的字节数和编译器有关,32位编译器为4字节,64位为8字节
两个一元运算符:*
提取指针变量指向位置的值;&
给出变量存储位置的地址
- 空指针
声明变量时若不直接赋值指针,一般先把指针指向NULL值:int *ip = NULL;
,该值实际上是指向地址为0的内存——一般的操作系统不允许访问该地址,也可以直接用if语句if(ip)
判断指针是否为空指针(NULL = 0
)
另外C++11后提倡用nullptr
替代NULL
- 指针的算术运算
指针是用数值表示的地址,因而可以对其进行算术运算(++, --, +, -)——当然,用乘除法会报错
对指针的算术运算限定于加减整数,其含义是按照指针定义的类型挪动对应字节数(如对char型指针,+1挪动一个字节,对int型指针,+1挪动4个字节)而且编译器并不会检查越界问题,所以这种操作时一定要小心数组越界
- 指针可以用关系运算符比较,如==, >, <。恒等运算不言而喻;后两者常用于数组中的判定语句,同一个数组中的元素在内存上是连续的,因而内存地址也会从小到大排列——这种比较不适用于char型指针,若需对char型指针进行大小比较,则需要强制转换类型,如下例所示
#include <iostream>
using namespace std;
const int MAX = 3;
int main()
{
int array[MAX] = {1,2,3};
int *iptr;
iptr = array; //iptr被赋值为array数组的首项地址
for(int i = 0; i<3; i++)
{
cout << "Address of array[" << i << "] = ";
cout << iptr << endl;
cout << "Value of array[" << i << "] = ";
cout << *iptr << endl; //间隔为4
iptr++;
//array++; //这是非法的
//指向数组的指针实际是一个常量指针(int *array[]等价于int *const array),不可以做为左值
}
cout << *(iptr + 10) << endl; //系统不会报错,仍然会读取
//*(iptr + 10) = 0; //系统不会报错!这种行为就很危险了
char string[MAX + 1] = "abc";
char *cptr;
cptr = string; //cptr被赋值为string字符串的首项地址
for(int i = 0; i<3; i++)
{
cout << "Address of string[" << i << "] = ";
cout << (int*)cptr << endl;
//注意必须用强制类型转换才能提取char型变量的地址
//否则对于char*型指针,系统会自动输出字符串值直到遇到终止符\0为止
//&cptr提取的是指针变量cptr的地址,而非cptr指向变量的地址
cout << "Value of string[" << i << "] = "; //间隔为1
cout << *cptr << endl;
cptr++;
}
while((int*)cptr > (int *)string) //内存地址比较也不适用于char型数组,需强制转换
{
cptr--;
cout << "Address of character " << *cptr << " = ";
cout << (int*)cptr << endl;
//注意必须用强制类型转换才能提取char型变量的地址
//否则对于char*型指针,系统会自动输出字符串值直到遇到终止符\0为止
//&cptr提取的是指针变量cptr的地址,而非cptr指向变量的地址
}
//取地址符&与数组
cout << "array " << array << endl
<< "&array[0] " << &array << endl
<< "&array " << &array << endl //以上三条输出结果相同,都是array的首地址
<< "array + 1 " << array + 1 << endl //输出array[1]的地址
<< "&array + 1 " << &array + 1 << endl //将array这个长为3的数组看成整体,向后挪动一个数组长度的内存
<< "array + 3 " << array + 3 << endl; //内存向后挪动3个整型(一个数组长度),与前一条输出一样
cout << "string " << string << endl //输出abc
<< "&string[0] " << &string << endl //string的首地址
<< "&string " << &string << endl //string的首地址
<< "string + 1 " << string + 1 << endl //输出bc
<< "&string + 1 " << &string + 1 << endl //将string这个长为3+1的字符串看成整体,向后挪动一个数组长度的内存
<< "string + 3 " << (int *)(string + 4) << endl; //内存向后挪动4个字符长度(一个数组长度),与前一条输出一样
return 0;
}
- 指针数组——指针的指针
有时候,我们需要让数组存储指向int或者char或者其他类型数据的指针,则可以分配一个指针数组,如
int *pptr[MAX]
,声明了一个长度为MAX,数据类型为整型指针的数组pptr,实例如下
#include<iostream>
using namespace std;
const int MAX = 3;
int main()
{
int array[MAX] = {1,2,3};
int *pptr[MAX];
for(int i=0;i<3;++i)
{
pptr[i] = &array[i]; //pptr为一个整形指针数组,储存array三个分量的内存地址
cout << "Value of array[" << i << "] = ";
cout << *pptr[i] << endl;
}
cout << "---------------------" << endl; //华丽的分割线
//也可以用指向字符的指针数组存储字符串列表,如
char const *names[MAX] = {
"Alice",
"Bob",
"Cindy",
}; //不可以通过指针修改names列表中的值,即该数组指针指向固定的地址
//不加const会报警
for(int i=0;i<3;++i)
{
pptr[i] = &array[i]; //pptr为一个整形指针数组,储存array三个分量的内存地址
cout << "---------------------" << endl; //华丽的分割线
cout << "Value of names[" << i << "] = ";
cout << names[i] << endl; //这里不加*,这和char型数组读取规则有关
cout << *names[i] << endl; //输出A,B,C
cout << &names[i] << endl; //输出names三个分量的地址(32位系统下相距4个字节)
cout << (int*)names[i] << endl;
//输出names三个分量的地址(相距5+1和3+1个字节,对应names存储的三个字符串加终止符的长度)
}
}
- 注意,C++与C的运算优先级不一样,C++中 * 的运算优先级低于[],因而
* ptr[]
是ptr先和[]结合为数组,再和*结合形成数组的元素类型是int *
类型,该数组中的元素是指向int型的指针,称为指针数组
- 而
int (*ptr)[]
中,()和[]的优先级一样,从左至右先计算*ptr,ptr为一个指针,指向一个数组,称为数组指针,这个用法之前没怎么接触过,下面给一个例子(里面关于数组指针的存取位置有点绕)
#include<iostream>
using namespace std;
int main()
{
int matrix[3][2] = {
{1,2},
{3,4},
{5,6}
};
//注意!多维数组matrix实际上是一个三维整形指针数组,分别指向三个长度为2的整型数组
int (*ptr)[2] = matrix; //定义了数组指针ptr指向3*2的二维数组
//(*ptr)数组指针后跟的是[2]的含义是该数组指针指向的数组中的每一个元素都是一个指向长度为2的整形数组的指针
for(int i=0; i<3; ++i)
{
cout << "---------------------" << endl; //华丽的分割线
cout << ptr + i << endl;
cout << &matrix[i] << endl; //这两条输出将matrix视为一维数组时第i个元素的地址
cout << **(ptr + i) << endl;
//ptr = matrix,按matrix的元素(3个二维数组)挪动,因而实际上是输出第i+1行的第一项
cout << matrix[i][0] << endl; //这两个输出的结果是一样的,都是输出1,3,5
}
for(int j=0; j<3; ++j)
{
cout << "---------------------" << endl; //华丽的分割线
cout << *(*(ptr + j) + 1) << endl; //先挪到matrix的第j+1行,取值后得到的是第j+1个二维数组,然后再移动1个
cout << matrix[j][1] << endl; //和上一条输出一样,都是2,4,6
}
return 0;
}
- C++支持传入指针给函数,也支持从函数返回指针,只是需要声明的时候加上 * 号,另外为了稳妥起见,在返回函数内的局域变量地址给外部函数的时候最好在定义时加上static
- 有三种形式的传递:
- 值传递:先将变量拷贝到另一个位置,作为形参传入函数,函数结束后销毁,不影响原来变量的值
- 指针传递:直接传递指向实参的指针(地址),函数直接对原来的变量进行修改
- 引用传递:传入的形参实际上是实参地址的拷贝,被调用函数对形参的任何操作都被处理成间接寻址——通过栈中存放的地址访问主调函数中的实参变量,因而也会改变原变量的值。
#include<iostream>
using namespace std;
void by_value(int n)
{
cout<< "值传递——函数操作地址:" << &n << endl; //返回地址与实参不一样
n++;
return;
}
void by_pointer(int *n)
{
cout<< "指针传递——函数操作地址:" << n << endl; //返回地址与实参一样
*n = *n + 1;
return;
}
void by_reference(int &n)
{
cout<< "引用传递——函数操作地址:" << &n << endl; //返回地址与实参一样
n++;
return;
}
int main()
{
int n = 1;
cout<< "实参地址:" << &n << endl;
by_value(n);
cout<< "值传递后的实参值:" << n << endl; //输出1
by_pointer(&n);
cout<< "指针传递后的实参值:" << n << endl; //输出2
by_reference(n); //注意这里直接传入变量即可
cout<< "引用传递后的实参值:" << n << endl; //输出3
return 0;
}
- 引用传参和指针传参的区别:
- 引用必须引用对象,由于引用不能为NULL,因此更安全
- 指针是保存内存地址的变量。引用与其引用的项具有相同的内存地址。
- 指针可以迭代数组,比如传递数组变量时实际上是传递数组首位的地址指针,也因此,传递指针可能会导致越界访问
- 尽可能使用引用,必要时使用指针。但是,如果想编写同时使用C和C++编译器编译的C代码,则必须使用指针