C++ 指针和引用
指针(pointer)
指针:对于一个类型T
,[ T*
]就是指向T的指针类型,也即一个[ T*
]类型的指针变量能够保存一个T对象的地址,而这个类型T是可以加一些限定词的(const
,volatile
等…)
通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如 动态内存分配,没有指针是无法执行的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:
const char c = 'A';
const char* p = &c;
//输出char*指针p指向的变量的值
cout << *p << endl;
//输出char*指针p指向的变量的地址
cout << (int *)p << endl;//通过将char*指针转换为int类型的指针进行输出
注意:如果要显示[char *]指针所指向的地址单元的地址,需要将[char *]类型的指针强制转化为另一种类型的指针,我将[char *]类型的指针强制转化为[int *]类型指针
引用(reference)
引用在C++中经常被用到,尤其是作为函数参数的时候,需要在函数内部修改并更新函数外部的值的时候,首先我们必须明确的一点就是:引用是一种特殊指针
引用的特殊性在于: 引用是给另外一个变量起一个别名,此引用将绑定一个变量,相对于指针, 引用并不会分配内存空间,所以 对引用的所有操作,事实都是作用在其所绑定的对象之上的
引用的声明方法: [ 类型标识符 & 引用名=目标变量名 ]
例如以下:
//🔥引用是一个指向其它对象的常量指针,它保存着所指对象的存储地址。并且使用的时候会自动解引用
const int c = 65;
const int & b = c; //给变量c绑定到引用b上
//输出变量c和其绑定的引用b的值
cout << c << endl; 结果为65
cout << b << endl; 结果为65
//输出变量c和其绑定的引用b的地址
cout << &c << endl;结果为000000F9F1CFFBC4
cout << &b << endl;结果为000000F9F1CFFBC4
从运行结果来看, b这个引用的值就是变量c的值,且b的地址和c的地址相同,说明b和c为同一个内存单元格,只是有2个名字而已,就相当于对内存区域000000F9F1CFFBC4这个单元格起了2个名字,叫c和b,用c和b都可以访问000000F9F1CFFBC4这个内存单元格,所以通过引用后b和c就是一个东西。
指针和引用的关系
1.指针是变量需要内存空间,引用是别名不需要内存空间
指针是一个实体变量,需要创建内存空间 (若创建一个指针变量[ int*p ],那么这个p必须在内存中再占有一个单元格(4字节),它所占的单元格内可以填写一个地址,用来索引它所指向的变量)
而引用只是变量的别名,不需要再次分配内存空间(引用是对已经存在的一个内存单元格进行绑定,单元格既然已经存在了,引用就只要再给单元格起一个名字就好,而引用由于不是变量所以不用在内存中存储)
2.指针定义时不一定要初始化,引用定义时一定要初始化
引用在定义的时候必须进行初始化,且只能指向一个变量(即使绑定别的变量,也不能改变之前绑定的地址)
例如:
int c = 65;
int& b = c;
cout << "引用绑定值后 输出的引用绑定的变量的值和地址为: \n";
cout << c <<&c<< endl;
cout << b << &c << endl;
int a = 5;
b = a;
cout << "引用绑定别的值后 输出的引用绑定的变量的值和地址为:\n ";
cout << "值为"<<a <<"地址为"<<&a << endl;
cout << "值为" <<b<<"地址为"<<&b << endl;
指针不一样,指针使用时不必初始化,可以指向nullptr(空地址指针),初始化后仍可以改变指向的地址。
3.指针作为函数传递时需要拷贝内存,引用作为函数传递时不需要拷贝内存
作为函数参数传递时,引用不需要内存拷贝,所以也就不需要申请内存, 因此当函数参数传递时,很多时候使用&或者const&传递参数节省内存.
作为函数参数传递时,如果想改变传递进函数参数的原始变量的值, 引用改变后会改变原始变量,而指针的值改变后并不会改变原始变量,因为它只是一份内存副本,如果想达到改变的效果,需要使用**(双重解引用)作为前缀进行改变。
4.引用的++运算是变量本身的运算,而指针的++运算是内存地址的++运算,例如:
int c = 5;
int& b = c; //引用b绑定常数c c的值为5
int d = 5;
int* p = &d; //指针p指向常数d d的值为5
b++;//引用++运算
*p++;//指针++运算
cout << "c的值为: " << c << " " << "c的地址值为: " << &c << endl;
cout << "d的值为: " << d << " " << "d的地址值为: " << &d << endl;
cout << "引用b的值为: " << b << " " << "其绑定的地址值为: " << &b << endl;
cout << "指针p的值为: " << *p << " " << "其绑定的地址值为: " << p << endl;
5.如果返回动态内存分配的对象或者内存,必须使用指针,返回引用可能会引起内存泄漏,例如:
string& foo()
{
string* str = new string("abc");
return *str;//返回动态内存分配的对象或者内存,返回类型为引用
}
cout << foo() << endl;//结果为 "abc"
这样写的坏处是可能会造成内存的泄漏,如果string str=foo(),那么这个临时内存就没法被释放(没搞懂这里)
6.不要返回局部变量的引用,返回对象的引用最好加上const修饰符
int &sum()
{
int num = 10;
int& r = num;//建立一个引用变量r 绑定局部变量num的地址
cout << &r << endl;
return r;//返回这个局部变量
}
int main()
{
int& result = sum();
cout << "result = " << &result << endl;
cout << "result = " << result << endl;
}
🔥 程序运行结果为一个随机值 : [ result = -858993460 ]
🔥 这是由于引用绑定的是一个局部变量 局部变量的作用域仅在函数内,就算绑定了这个局部变量的地址,也无法通过这个地址明确这个局部变量
7.在进行重载操作符时,操作符[ <<和>> = ]返回引用,而操作符[ + - / * ]的返回对象不能是引用
void P(float* a, float* b) //值传递为一级指针(指向单一地址)的函数
{
*a = 5, * b = 10;🔥 防止主程序中 结束完别的函数导致ab的位置已经被互换
float temp = *a;
*a = *b;
*b = temp;
}
void reference(float& a, float& b)//值传递为引用(绑定单一地址)的函数
{
a = 5, b = 10;
float temp = a;
a = b;
b = temp;
}
void PP(float** a, float** b)//值传递为二级指针(指向单一地址的地址)的函数
{
**a = 5, **b = 10;
float temp = **a;
**a = **b;
**b = temp;
}
int main()
{
float a = 5, b = 10;
cout << "原值:a=" << a << " b=" << b << endl;
float* aptr = &a, * bptr = &b;
P(aptr, bptr); //一级指针作函数值传递(使用指向a和b的地址的指针进行值传递)
cout << "一级指针值传递结果:a=" << a << " b=" << b << endl;
P(&a, &b); //一级指针作函数值传递(直接使用a和b的地址进行值传递)
cout << "一级引用值传递结果:a=" << a << " b=" << b << endl;
reference(a, b);//引用作函数值传递(使用绑定a和b的地址的引用名进行值传递)
cout << "引用值传递结果:a=" << a << " b=" << b << endl;
float** aptrptr = &aptr, ** bptrptr = &bptr;
PP(aptrptr, bptrptr);//二级指针作函数值传递((使用指向着指向a和b的地址的指针进行值传递))
cout << "二级指针值传递结果:a=" << a << " b=" << b << endl;
}
引用使用的陷阱
void foo(const string & s)
{
cout << s << endl;
}
int main()
{
foo("TEST!");💣此表达式非法,因为括号内的"TEST!"和函数返回值这些字符都是临时对象
🔥在C++中,这些临时变量都是const类型的,所以我们要将函数输入参数[string &s]加上const前缀为
[const string &s]
🔥也可以将输入参数变为非临时变量
string a = "TEST!";🔥
foo(a);
}
[函数传一级指针,传引用,传二级指针]作为函数参数时,三种函数参数传递方式,实现改变外部变量:
C++为什么需要引入引用
首先我们要知道: 引用的底层也是指针实现的,内置类型指针传递和引用传递的汇编代码是一样的,那C++为什么还需要引入引用呢?
因为引用的高效,在于对大的数据,不用直接的复制数据, 指针传递时需要分配内存空间,间接寻址。而引用不需要分配空间直接寻址速度快。
所以在编写操作符重载时,必须用引用,否则大量的分配空间会引发栈溢出和耗时,特别在使用操作符重载编写3d图像类时,对象的连续运算会对实时动画(游戏)产生严重延迟卡顿现象
流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择
其次,使用指针进程会犯以下错误 : 1.操作空指针 2.操作野指针 3.不知不觉改变了指针的值,还以为指针正常.如果我们想正确的使用指针必须保证这个三个情况不会出现.
而引用的出现就解决了这个问题: 1.不存在空引用(保证不操作空指针) 2.必须初始化(保证不是野指针)3.一个引用永远指向向他初始化的那个对象(保证指针值不变).这些特性保证了编译安全
用户自定义的类型最好用引用传参,这样可以避免不必要的构造函数和析构函数调用,但是对于像int,long,char一类的内置类型,按址传参会比按引用传参更高效
总结:🎯
1.指针有着自己的一块空间,而引用只是一个别名
2.使用sizeof看一个指针的大小是4,而引用时被引用对象的大小
3.指针可以被初始化为NULL,而引用必须是被初始化,且必须是一个已有对象的引用
4.作为参数传递时,指针需要被解引用才可以对指向的对象进行操作,而直接对引用的修改会直接改变引用所指向的对象
5.指针在使用中可以指向其他对象,但是由于只能是一个对象的引用,不能被改变
6.指针可以有多级指针(**p),而引用只有一级引用
7.指针和引用使用 ++ 运算符的意义完全不一样
8.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露
9.内置类型建议按值传参,用户自定义类型建议按引用参数
来自:
https://blog.csdn.net/jinking01/article/details/88691535
https://www.cnblogs.com/WindSun/p/11434417.html
https://blog.csdn.net/wang371372/article/details/82178139
https://www.pianshen.com/article/4531108480/
https://zhidao.baidu.com/question/568161042.html