C++自学 | 4 指针 & 引用
本内容仅属于个人自学记录,欢迎交流和指正,请勿转载或发表不当言论。
主要学习网址:https://www.runoob.com/
4.1 Cplus/C++ 指针
1. 读取变量的内存地址
cout << &var <<endl;
2. 指针变量声明:
所有指针的值的实际数据类型,都是一个代表内存地址的长十六进制数。
type *var-name;
【举例
int *ip; /* 一个整型的指针 */ double *dp; /* 一个 double 型的指针 */ float *fp; /* 一个浮点型的指针 */ char *ch; /* 一个字符型的指针 */
3. NULL空指针:
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值,空指针的值是 0 。
在大多数操作系统上,程序不允许访问地址为0的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置,但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,可以使用 if 语句:
if(ptr) /* 如果 ptr 非空,则完成 */ if(!ptr) /* 如果 ptr 为空,则完成 */
4. 算术运算:
指针可以进行的算术运算
ptr++; //如果 int *ptr 指向地址1000,ptr++ 指向地址 1004。ptr每增加一次,都将指向下一个整数位置,即当前位置往后移4个字节。 ptr--; //如果 char *ptr 指向地址1000,ptr-- 指向地址999,因为上一个字符的位置在地址999。 ptr+n; ptr-n;
【实例:递增指针代替数组
#include<iostream> using namespace std; int main() { const int MAX = 3; int var[MAX] = { 10,100,200 }; int* ptr;
//递增指针时,将第一个元素的地址赋给指针 ptr = var;
//递减指针时,将最后一个元素的地址赋给指针 ptr = &var[MAX-1] for (int i = 0; i < MAX; i++) { cout << "Address of var[" << i << "] = "; cout << ptr << endl; cout << "Value of var[" << i << "] = "; cout << *ptr << endl; ptr++; } return 0; }
执行后得到以下结果:
Address of var[0] = 0099FA10 Value of var[0] = 10 Address of var[1] = 0099FA14 Value of var[1] = 100 Address of var[2] = 0099FA18 Value of var[2] = 200
【实例:输出数组地址时可以不用 & 运算符
修改上述程序中的代码时:
cout << "Value of var[" << i << "] = "; cout << var << endl;
输出如下:
Address of var[0] = 00DEFA34 Value of var[0] = 00DEFA34 Address of var[1] = 00DEFA38 Value of var[1] = 00DEFA34 Address of var[2] = 00DEFA3C Value of var[2] = 00DEFA34
对数组及数组中各元素输出的结果:
cout << "var " << var << endl; cout << "var+1 " << var + 1 << endl; cout << "&var " << &var << endl; cout << "&var+1 " << &var + 1 << endl; cout << "var[0] " << var[0] << endl; cout << "&var[0] " << &var[0] << endl; cout << "&var[0]+2 " << &var[0]+2 << endl; cout << "&var[0]+3 " << &var[0]+3 << endl; cout << "ptr " << ptr << endl; cout << "*ptr " << *ptr << endl;
分别得到的结果是:
var 004FFAF0 //数组第一个元素地址 var+1 004FFAF4 //数字第一个元素地址 + 4个字节(int),即数组第二个元素的地址 &var 004FFAF0 //整个数组的地址 &var+1 004FFAFC //跳过整个数组的地址后,第一个地址 (0+C = 十进制12,3个4字节地址) var[0] 10 //数组第一个元素的值 &var[0] 004FFAF0 //数组第一个元素地址 &var[0]+2 004FFAF8 //数组第三个元素地址 &var[0]+3 004FFAFC //跳过整个数组的地址后,第一个地址 ptr 004FFAF0 //数组第一个元素的地址 *ptr 10 //数组第一个元素的值
从上面可以总结出:
- 数组第一个元素的地址:var, &var[0]
- 整个数组的地址:&var
还可以利用指针来表示数组:
【实例:
#include<iostream> using namespace std; int main() { const int MAX = 3; int var[MAX] = { 10, 100, 200 }; cout << *var << endl << endl; for (int i = 0; i < MAX; i++) { *var = i; // 这是正确的语法 cout << *var << endl; cout << var[0] << endl << endl; } return 0; }
*var 的语句操作是把 i 的值赋给了 var 数组的第一个元素,修改了数组的值。
var++ 是不可以实现的,修改var的值是非法的,这是因为 var 是一个指向数组第一个元素的常量地址,不可以修改。
但以下语句是合法的:
*(var+2) = 500;
5. 指针数组:
指向整数的指针数组的声明如下:该数组 ptr 为一个 int 类型的指针数组,由 MAX 个整数指针组成,其中的每个元素都是一个指向 int 值的指针。即存储指针的数组。
int *ptr[MAX];
【实例:
#include<iostream> using namespace std; const int MAX = 3; int main() { int var[MAX] = { 10,100,1000 }; int* ptr[MAX]; for (int i = 0; i < MAX; i++) { ptr[i] = &var[i]; //赋值为整数数组的地址 } for (int i = 0; i < MAX; i++) { cout << "Value of var[" << i << "] = "; cout << *ptr[i] << endl; } return 0; }
【实例:对于 char *names[MAX] 字符型的指针数组,可以理解为一个二维字符数组,其中每一行存储一个 char 类型的一维数组。
#include <iostream> using namespace std; const int MAX = 4; int main () { const char *names[MAX] = { "Zara Ali", "Hina Ali", "Nuha Ali", "Sara Ali", }; for (int i = 0; i < MAX; i++) for (int j = 0; j < 8; j++) { cout << "Value of names[" << i << "] = "; cout << *(names[i] + j)<< endl; } return 0; }
执行后的输出如下:
Value of names[0] = Z Value of names[0] = a Value of names[0] = r Value of names[0] = a Value of names[0] = Value of names[0] = A Value of names[0] = l Value of names[0] = i Value of names[1] = H Value of names[1] = i Value of names[1] = n Value of names[1] = a Value of names[1] = Value of names[1] = A Value of names[1] = l Value of names[1] = i Value of names[2] = N Value of names[2] = u Value of names[2] = h Value of names[2] = a Value of names[2] = Value of names[2] = A Value of names[2] = l Value of names[2] = i Value of names[3] = S Value of names[3] = a Value of names[3] = r Value of names[3] = a Value of names[3] = Value of names[3] = A Value of names[3] = l Value of names[3] = i
6. 数组指针:
在C++运算符的优先级中,* 小于 [],ptr 先与 [] 结合成为数组,然后再与 int* 结合,使得数组的元素类型变为 int* 类型。因此以下的语句声明了一个 指针数组:
int *ptr[3];
通过 ()调整优先级后,* 和 ptr 先结合成为指针,然后再与 [] 结合成为一个数组。因此下列语句声明了一个 指向特定数组的指针,即数组指针:
int (*ptr)[3];
区别:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。
7. 指向指针的指针(多级间接寻址):
通常情况下,一个指针包含第二个指针的地址,第二个指针指向实际值的位置。
声明如下:
int **var;
【实例:
#include <iostream> using namespace std; int main () { int var; int *ptr; int **pptr; var = 3000; // 获取 var 的地址 ptr = &var; // 使用运算符 & 获取 ptr 的地址 pptr = &ptr; // 使用 pptr 获取值 cout << "var 值为 :" << var << endl; cout << "*ptr 值为:" << *ptr << endl; cout << "**pptr 值为:" << **pptr << endl; cout << "var 地址为 :" << &var << endl; cout << "ptr=&var 值为var的地址:" << ptr << endl; cout << "ptr地址为:" << &ptr << endl; cout << "*pptr=ptr=&var 值为var的地址:" << *pptr << endl; cout << "pptr 地址为:" << &pptr << endl; return 0; }
执行上述代码得到的结果如下:
var 值为 :3000 *ptr 值为:3000 **pptr 值为:3000 var 地址为 :00D3FA38 ptr=&var 值为var的地址:00D3FA38 ptr地址为:00D3FA2C *pptr=ptr=&var 值为var的地址:00D3FA38 pptr 地址为:00D3FA20
8. 传递指针给函数:
C++内允许将指针传递给函数,只需要声明函数参数为指针类型即可。
【实例:
#include <iostream> #include <ctime> using namespace std; void getSeconds(unsigned long* par); int main() { unsigned long sec = 0; cout << "Number of seconds " << sec << endl; getSeconds(&sec); //函数的参数为指针,因此传入变量的地址 cout << "Number of seconds " << sec << endl; return 0; } void getSeconds(unsigned long* par) { *par = time(NULL); return; }
【实例:能接受指针作为参数的函数,也能接受数组作为参数。
#include <iostream> using namespace std; double getAverage(int* arr, int size); int main() { int balance[5] = { 1000,2,3,17,50 }; double avg; avg = getAverage(balance, 5); cout << "Average Value is: " << avg << endl; return 0; } double getAverage(int* arr, int size) { int i; int sum = 0; double avg; for (int i = 0; i < size; i++) { sum += arr[i]; } avg = double(sum) / size; return avg; }
9. 从函数返回指针:
C++允许从函数返回指针,需要事先声明一个返回指针的函数,如下:
int* myFunction() { ... }
此外,C++不支持再函数外返回局部变量的地址,除非将局部变量定义为 static 变量。
【实例:随机数生成器
#include <iostream> #include <ctime> #include <cstdlib> using namespace std; int* getRandom() { static int r[10]; //设置随机数种子 srand((unsigned)time(NULL)); for (int i = 0; i < 10; i++) { r[i] = rand(); cout << r[i] << endl; } return r; } int main() { int* p; //一个指向整数的指针 p = getRandom(); for (int i = 0; i <10; i++) { cout << "*(p+" << i << ") : "; cout << *(p + i) << endl; } return 0; }
执行上述代码得到如下结果:
14005 4858 13033 22168 15372 18777 6884 25243 7261 10901 *(p+0) : 14005 *(p+1) : 4858 *(p+2) : 13033 *(p+3) : 22168 *(p+4) : 15372 *(p+5) : 18777 *(p+6) : 6884 *(p+7) : 25243 *(p+8) : 7261 *(p+9) : 10901
4.2 Cplus/C++ 引用
1. 概念:
引用变量是一个别名,即某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用通常用于函数参数列表和函数返回值。
2. 与指针的区别:
- 不存在 空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。而指针可以在任何时候指向另一个对象。
- 引用必须在创建时被初始化。而指针可以在任何时间被初始化。
引用更接近 const 指针,一旦与某个变量关联起来,就会一直指向该变量。
int rats = 10; int &rodents = rats;
上述代码可以等同于下述代码:
int rats = 10; int * const pr = &rats;
3. 创建引用:
可以通过原始变量名称或引用来访问变量的内容,例如:
int i = 17; //我们可以为 i 声明引用变量 int& r = i; double& s = d;
在声明中,& 读作引用。
第一个声明可以读作 “r 是一个初始化为 i 的整型引用”;第二个声明可以读作 “s 是一个初始化为 d 的double型引用”
4. 引用作为参数:
以引用作为参数的函数,可以传入变量,但不能传入常量。
【实例:使用 引用 来实现 引用调用函数
#include <iostream> using namespace std; // 函数声明 void swap(int& x, int& y); int main () { // 局部变量声明 int a = 100; int b = 200; cout << "交换前,a 的值:" << a << endl; cout << "交换前,b 的值:" << b << endl; /* 调用函数来交换值 */ swap(a, b); cout << "交换后,a 的值:" << a << endl; cout << "交换后,b 的值:" << b << endl; return 0; } // 函数定义 void swap(int& x, int& y) { int temp; temp = x; /* 保存地址 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 x 赋值给 y */ return; }
【补充:C++的函数传参:
- 将变量名作为实参和形参。此时传给形参的是变量的值,传递是单向的。如果在执行函数期间,形参的值发生变化,并不会回传给实参。因为在调用函数时,形参和实参不是同一个存储单元的。
- 传递变量的指针。形参是指针变量,实参是一个变量的地址。调用函数时,形参 指向 实参变量单元,这种形式可以通过指针改变实参的值。
- 传递变量的引用。形参是引用变量,和实参是一个变量。调用函数时,形参 指向 实参变量单元,这种形式可以通过引用改变实参的值。
【再补充:传递变量中指针与引用的区别和优势:
- 选择成员时,引用 使用 . 来查找;指针使用 -> 来查找。
- 指针可能传递 NULL,因此在使用前必须检查有效性;引用则必然代表某个对象。
- 两者在函数中执行时,都不需要创建副本,因此效率更高。
5. 引用作为返回值:
使用引用来代替指针,使函数返回一个引用,增强了 C++ 程序的可读性和维护能力。
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
【实例:
#include <iostream> using namespace std; double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0}; double& setValues( int i ) { return vals[i]; // 返回第 i 个元素的引用 } // 要调用上面定义函数的主函数 int main () { cout << "改变前的值" << endl; for ( int i = 0; i < 5; i++ ) { cout << "vals[" << i << "] = "; cout << vals[i] << endl; } setValues(1) = 20.23; // 改变第 2 个元素 setValues(3) = 70.8; // 改变第 4 个元素 cout << "改变后的值" << endl; for ( int i = 0; i < 5; i++ ) { cout << "vals[" << i << "] = "; cout << vals[i] << endl; } return 0; }
执行后得到以下结果:
改变前的值 vals[0] = 10.1 vals[1] = 12.6 vals[2] = 33.1 vals[3] = 24.1 vals[4] = 50 改变后的值 vals[0] = 10.1 vals[1] = 20.23 vals[2] = 33.1 vals[3] = 70.8 vals[4] = 50
此外,当返回一个引用时,注意被引用的对象不能超出作用域,除非是对 static 变量的引用。