c++ 指针与引用
目录
1. c++指针
指针是对象, 跟普通对象一样, 它有地址&p和存储的值p, 与普通对象的区别是p存储的值是其它对象的地址.
要得到指针指向的对象的值, 需要使用解引用操作符: *p
指针p涉及的写法:
p : 指针p这个对象存储的值, 是指针p指向的对象的地址 .
p: 指针p指向的对象的值, ""是解引用操作符.
&p: 指针p自己的地址.
指针p涉及的概念:
指针常量: 该指针是一个常量, 该指针的值(所存储的地址)是不可改变的.
常量指针: 该指针指向的对象是一个常量, 该指针指向的对象是不可改变的.
1.1 简单指针
#include <iostream>
int main()
{
using std::cout;
using std::endl;
int a = 5;
int *p ; //声明一个指针
p = &a ; //指针指向变量a
cout << " a=" << a << endl; // a=5 , 常规变量
cout << "&a=" <<&a << endl; //&a=0x61FF08, 取a的地址
cout << " p=" << p << endl; // p=0x61FF08, 指针变量p, 存储的是a的地址
cout << "*p=" <<*p << endl; //*p=5 , 指针指向的内容.
}
1.2 空指针NULL
#include <iostream>
using namespace std;
int main()
{
//如果指针未初始化, 则它可能有垃圾值, 导致程序难以调试.
//指针变量声明时, 没有确切地址可以赋值, 可以给它一个NULL, 称为空指针.
//NULL指针在标准库中定义, 值为0;
//地址0有特别意义, 表明指针指向一个不可访问的内存位置, 就认为指针不指向任何东西.
int *ptr = NULL;
cout << "ptr=" << ptr << endl; //ptr=0
return 0;
}
1.3 指针的算术运算
可以对指针进行四种算术运算: ++, --, +, -.
指针可以用关系运算符比较: ==, <, >.
#include <iostream>
using namespace std;
const int MAX=3;
int main()
{
int var[MAX] = {100, 110, 200}; //一个整型数组
int *ptr; //一个整型指针
ptr = var; //指针指向数组, 实际上数组名代表的就是数组的第0元素的地址.
for(int i=0; i<MAX; i++)
{
cout << "var[" << i << "]=" << *ptr << ", Addr=" << ptr << endl;
ptr++; //指针自增, int占四个字节, 所以++后值增加4.
//var[0]=100, Addr=0x61fef4
//var[1]=110, Addr=0x61fef8
//var[2]=200, Addr=0x61fefc
}
cout << endl;
ptr = &var[MAX-1]; //指针指向数组最后元素的地址.
for(int i=MAX-1; i>=0; i--)
{
cout << "var[" << i << "]=" << *ptr << ", Addr=" << ptr << endl;
ptr--; //指针自减, int占四个字节, 所以--后值减少4.
//var[2]=200, Addr=0x61fefc
//var[1]=110, Addr=0x61fef8
//var[0]=100, Addr=0x61fef4
}
cout << endl;
//ptr = var; //指针指向第0个元素, 等价于ptr = &var[0];
int i = 0;
while(ptr<=&var[MAX-1]) //指针还没指到最后一个元素
{
cout << "var[" << i << "]=" << *ptr << ", Addr=" << ptr << endl;
ptr++;
i++;
//var[0]=100, Addr=0x61fef4
//var[1]=110, Addr=0x61fef8
//var[2]=200, Addr=0x61fefc
}
}
1.4 指针与数组
常用语法
int var[MAX] = {100, 110, 200}; //一个整型数组
int *ptr; //一个整型指针
ptr = var ; //指针指向数组, 实际上数组名代表的就是数组的第0元素的地址, 打印ptr或var都能打印出数组地址.
//注意, 此处虽然ptr=var, 但ptr与var并不完全等价(var是常量, 不能自增),
//取元素地址
ptr = &var[0] ; //指针指向数组, 指向第0个元素的地址, 等价于ptr=var.
ptr = &var[n-1]; //指针指向数组最后一个元素.
//指针自增自减
ptr++; //指针自增, 指向下一个元素.
ptr--; //指针自减, 指向上一个元素.
var++; //注意, 会报错, 不能使用数组名自增, var是一个常量.
//取元素内容
*var ; //在数组名前加*, 获取第0个元素的内容.
*(var+2); //取第二个元素的内容.
var[2]; //同上
ptr[2]; //同上
*ptr ; //指针指向的当前元素的内容(根据ptr指针大小, 可以取到所有元素, 超过数组边界会取到垃圾值).
1.5 由指针组成的数组(数组的元素是指针)
#include <iostream>
using namespace std;
const int MAX=4;
int main()
{
int var0=100, var1=200, var2=300, var3=400;
//ptr是一个数组, 数组的元素是指针.
int * ptr[MAX] = {&var0, &var1, &var2, &var3};
for(int i=0; i<MAX; i++)
{
cout << "ptr[" << i << "]=" << ptr[i] << ", *ptr[" << i << "]=" << *ptr[i] << endl;
}
// ptr[i], 数组的元素, 本例中元素都是指针
//*ptr[i], 数组的元素指向的内容
//打印如下内容
//ptr[0]=0x61ff08, *ptr[0]=100
//ptr[1]=0x61ff04, *ptr[1]=200
//ptr[2]=0x61ff00, *ptr[2]=300
//ptr[3]=0x61fefc, *ptr[3]=400
return 0;
}
1.6 指向指针的指针
#include <iostream>
using namespace std;
int main()
{
int var;
int *ptr;
int **pptr; //声明"指向指针的指针"
var = 3000;
ptr = &var;
pptr = &ptr; //给"指向指针的指针"赋值
cout << " var = " << var << endl;
cout << " *ptr = " << *ptr << endl;
cout << "**pptr= " << **pptr << endl; //使用**pptr访问真正的值
//打印:
// var = 3000
// *ptr = 3000
//**pptr= 3000
}
1.7 指针作为函数的参数
//传递简单指针
#include <iostream>
using namespace std;
void add1(int *p); //指针作为参数, p是个指针, 可以认为参数类型是(int *), 指向int的指针
int main()
{
int a = 5;
cout << "before: a=" << a << endl;
add1(&a); //调用函数时, 给的实参是指针或地址.
cout << "after : a=" << a << endl; //在函数体中对参数*p的修改, 会体现在a上
//打印:
//before: a=5
//after : a=6
}
void add1(int *p)
{
*p += 1; //给参数增加1, 使用*p获取真实值, 修改*p会修改实参.
}
//传递数组指针给函数
#include <iostream>
using namespace std;
double getAverage(int * arr, int size);//函数参数一是一个指针
int main()
{
int scores[5] = {1, 2, 3, 4, 6};
double avg;
avg = getAverage(scores, 5); //scores是数组名, 同时也是个指针, 所以可以直接传给函数.
//以下三行代码, 把scores赋值给一个指针在传给函数, 效果跟直接传scores相同.
//int *pscores;
//pscores = scores;
//avg = getAverage(pscores, 5);
cout << "avg=" << avg << endl;
return avg;
}
//计算平均数
double getAverage(int * arr, int size)
{
int sum = 0;
double avg;
for(int i=0; i<size; i++)
{
sum += arr[i]; //arr相当于数组名, arr[i]是第i个元素.
}
avg = double(sum)/size;
return avg;
}
//传递vector指针作为参数
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void set_value(vector<vector<string>> *pvec, int i, int j, string vlu) //形参加上*表示该参数是指针
{
// pvec[i][j] = vlu; //error
//*pvec[i][j] = vlu; //error
//*pvec.at(i).at(j) = vlu; //error, 解引用失败, 原因不明.
// pvec->at(i)[j] = vlu; //pass , 函数体中直接使用该指针.
pvec->at(i).at(j)= vlu; //pass , pvec是指针, 所以第一级不能用".", 需要用"->"
}
int main()
{
vector<vector<string>> vec;
vec.push_back({"00", "01", "02", "03"});
vec.push_back({"10", "11", "12" });
vec.push_back({"20", "21" });
vec.push_back({"30" });
set_value(&vec, 0, 2, "xx"); //调用函数时传入&vec, vec的地址
set_value(&vec, 2, 1, "xx");
for(auto iter0=vec.begin(); iter0!=vec.end(); iter0++)
{
for(auto iter1=iter0->begin(); iter1!=iter0->end(); iter1++)
{
cout << *iter1 << " ";
}
cout << endl;
}
//打印vector, v[0][2]和v[2][1]在set_value中被修改为xx:
//00 01 xx 03
//10 11 12
//20 xx
//30
}
1.8 从函数返回指针
注意: C++不支持在函数外返回局部变量的地址,除非定义局部变量为 static变量。
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
const int MAX = 10;
int * getRandom(); //函数返回值是指针
int main()
{
int *p; //指向整型的指针
p = getRandom(); //将函数返回的指针赋值给p
for(int i=0; i<MAX; i++)
{
//cout << "*(p+" << i << ") : " << *(p+i) << ", p[" << i << "] = " << p[i] << endl;
char buffer[100];
snprintf(buffer, 100, "*(p+%d)=%5d, p[%d]=%5d", i, *(p+i), i, p[i]); // *(p+i)与p[i]效果相同.
cout << buffer << endl;
}
//打印:
//*(p+0)=25832, p[0]=25832
//*(p+1)=27689, p[1]=27689
//*(p+2)=19849, p[2]=19849
//*(p+3)= 8113, p[3]= 8113
//*(p+4)=29525, p[4]=29525
//*(p+5)=21313, p[5]=21313
//*(p+6)=14175, p[6]=14175
//*(p+7)= 531, p[7]= 531
//*(p+8)=29886, p[8]=29886
//*(p+9)=15510, p[9]=15510
return 0;
}
int * getRandom()
{
static int r[MAX]; //返回的指针不能指向局部变量, 只能把这个变量定义为static变量.
srand( (unsigned)time(NULL) ); //设置随机种子
for(int i=0; i<MAX; i++)
{
r[i] = rand();
cout << "r[" << i << "] = " << r[i] << endl;
}
//打印:
//r[0] = 25832
//r[1] = 27689
//r[2] = 19849
//r[3] = 8113
//r[4] = 29525
//r[5] = 21313
//r[6] = 14175
//r[7] = 531
//r[8] = 29886
//r[9] = 15510
return r; //返回指针, r是数组名, 也是指针.
}
2. c++引用
引用是c++新增内容, 类似于指针, 但比指针更方便易用.
引用的一个优点是它一定不为空(声明的同时就要对它初始化).
在底层, 引用是通过指针常量(指针的值不可改为)的方式实现的.
2.1 引用的定义
引用可以看做是一个数据的别名, 通过引用和原来的名字都能找到这份数据.
数据类型 &引用名称 = 被引用的数据;
引用必须在定义的同时进行初始化, 且不能再引用其它数据.
#include <iostream>
using namespace std;
int main(){
int a = 99;
int &r = a; //定义一个引用, r与a指代同一份数据
cout << "a=" << a << endl; //99
cout << "r=" << r << endl; //99
cout << "&a=" << &a << endl; //0x61ff08
cout << "&r=" << &r << endl; //0x61ff08, r和a的地址相同
r = 47;
cout << "a=" << a << endl; //47, 通过引用可以修改原变量数据
cout << "r=" << r << endl; //47
const int &r1 = a; //定义一个常引用, 常引用不可修改值.
r1 = 22; //报错: assignment of read-only reference 'r1'
}
注意: 定义引用时使用&, 使用引用时不能再加&, 加上&后, &r表示取地址.
&字符的作用:
- 按位与.
- 取地址.
- 定义引用.
2.2 引用作为函数参数
- 定义或声明函数时, 将函数的形参指定为引用的形式
- 调用函数时, 实参和形参绑定在一起, 指代同一份数据.
- 在函数体中修改形参数据, 会导致实参数据也被修改.
- 语法要点:
void swap_2(int &rx, int &ry){ //传递引用, 将参数声明为引用类型. int tmp; //tmp不必声明为引用类型 tmp = rx; rx = ry; ry = tmp; } int main() { int a2=12, b2=34; swap_2(a2, b2); //传递参数时直接传递变量, 不需要取地址, 也不需要创建引用. }
例子1:
#include <iostream>
using namespace std;
void swap_0(int x, int y);
void swap_1(int *px, int *py);
void swap_2(int &x, int &y);
int main(){
int a0=12, b0=34;
cout << "before: a0=" << a0 << ", b0=" << b0 << endl;
swap_0(a0, b0);
cout << "after : a0=" << a0 << ", b0=" << b0 << endl;
cout << endl;
//打印如下内容, a0和b0的值没有交换:
//before: a0=12, b0=34
//after : a0=12, b0=34
int a1=12, b1=34;
cout << "before: a1=" << a1 << ", b1=" << b1 << endl;
swap_1(&a1, &b1); //传递参数时要取变量的地址.
cout << "after : a1=" << a1 << ", b1=" << b1 << endl;
cout << endl;
//打印如下内容, a1和b1的值交换了:
//before: a1=12, b1=34
//after : a1=34, b1=12
int a2=12, b2=34;
cout << "before: a2=" << a2 << ", b2=" << b2 << endl;
swap_2(a2, b2); //传递参数时直接传递变量, 不需要取地址, 也不需要创建引用.
cout << "after : a2=" << a2 << ", b2=" << b2 << endl;
cout << endl;
//打印如下内容, a1和b1的值交换了:
//before: a2=12, b2=34
//after : a2=34, b2=12
}
void swap_0(int x, int y){
int tmp;
tmp = x;
x = y;
y = tmp;
}
void swap_1(int *px, int *py){ //传递指针
int tmp; //tmp不必声明为指针类型
tmp = *px;
*px = *py;
*py = tmp;
}
void swap_2(int &rx, int &ry){ //传递引用, 将参数声明为引用类型.
int tmp; //tmp不必声明为引用类型
tmp = rx;
rx = ry;
ry = tmp;
}
例子2: 利用引用传递多维vector
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void set_value(vector<vector<string>> &rvec, int i, int j, string vlu) //形参加上&表示该参数是引用
{
rvec[i][j] = vlu; //函数体中直接使用rvec,
//本语句还可以写为rvec.at(i).at(j) = vlu;
}
int main()
{
vector<vector<string>> v;
v.push_back({"00", "01", "02", "03"});
v.push_back({"10", "11", "12" });
v.push_back({"20", "21" });
v.push_back({"30" });
set_value(v, 0, 2, "xx"); //调用函数时直接传入vector, 不需要传入引用
set_value(v, 2, 1, "xx");
for (auto iter0=v.begin(); iter0!=v.end(); iter0++)
{
for (auto iter1=iter0->begin(); iter1!=iter0->end(); iter1++)
{
cout << *iter1 << " ";
}
cout << endl;
}
//打印vector, v[0][2]和v[2][1]在set_value中被修改为xx:
//00 01 xx 03
//10 11 12
//20 xx
//30
}
2.3 函数返回引用
利用"函数返回引用", 可以把"函数调用"放在"赋值语句"的左边, 表示对"函数返回的引用"赋值
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double & setValue(int i)
{
double & ref = vals[i]; //ref引用vals[i]
return ref; //返回第i个元素的引用
}
int main()
{
cout << "values before change:" << endl;
for(int i=0; i<5; i++)
{
cout << "vals[" << i << "]=" << vals[i] << endl;
}
//打印如下内容:
//values before change:
//vals[0]=10.1
//vals[1]=12.6
//vals[2]=33.1
//vals[3]=24.1
//vals[4]=50
//函数放在赋值语句的左边, 表示对"函数返回的引用"赋值
setValue(1) = 20.23; //对vals[1]赋值
setValue(3) = 70.8 ; //对vals[3]赋值
cout << "values after change:" << endl;
for(int i=0; i<5; i++)
{
cout << "vals[" << i << "]=" << vals[i] << endl;
}
//打印如下内容:
//values before change:
//vals[0]=10.1
//vals[1]=20.23 //值被修改了
//vals[2]=33.1
//vals[3]=70.8 //值被修改了
//vals[4]=50
}
```cpp
注意: 函数返回的引用不能指向"局部数据(比如函数内的局部变量)", 可以是如下变量:
1) 全局变量.
2) 静态变量.
3) 引用类型的函数参数.
```cpp
int x[] = {1, 2, 3, 4};
int & setValue(int i){
int & ri = x[i];
return ri; //返回全局变量
}
int & func(int i){
static int x = i*2;
return x; //返回静态变量
}
int & add(int a, int b, int c, int & result)
{
result = a+b+c;
return result; //返回函数自己的参数
}
3 引用和指针的区别
角度 | 指针 | 引用 |
---|---|---|
占用内存 | 4字节 | 4字节 |
寻址 | 允许寻址 &p 返回指针自己的地址 | 不允许寻址 &r返回的是被引用对象的地址, 不是r的地址, r的地址由编译器掌握 |
替代 | 指针可以替代引用 | 引用不能替代指针 |
解引用 | 可以解引用*p表示指向的对象 | 不能解引用, r与其引用的对象等价 |
sizeof | sizeof 指针: 指针本身的大小 | sizeof 引用: 被引用对象的大小 |
可否为空 | 可 | 不可 |
可否修改 | 可在任何时候指向另一个对象 | 不可指向另一个对象 |
多级 | 可以有指向指针的指针, **p合法 | 引用只能一级, &&r不合法 |
自增 | 指针++: 指向的内存地址自增(指向了其他内容) | 引用++: 被引用对象自增 |
用作函数参数 | 依然是"值传递", 特殊点在于传递的值是地址 | 传递的是实参本身(而不是拷贝副本), 修改形参相当于修改实参. |