c++:函数中的指针与引用

5 篇文章 0 订阅
本文详细介绍了C++中的指针与引用,包括它们的概念、存储区域、使用场景以及在函数参数和返回值中的应用。强调了引用作为函数参数的优势,如强制初始化、避免空指针等问题,并讨论了何时选择引用或指针作为返回值。文章还探讨了指针与引用在函数参数中的差异,以及在实际编程中如何做出最佳选择。
摘要由CSDN通过智能技术生成

        为了搞清楚一个变量的生存周期花上大量的时间,为了查找一个反复被delete的指针变量而苦苦烦恼,如果是java或者py这些痛苦都不复存在,但是指针与引用起码关于指针与引用这一块都没有搞清楚,那么我用cpp写代码的意义何在?于是,我花了大量时间把指针与引用本质的东西问到底!c++付出大量的学习代价,我想这也是其无所不能的原因,因为难,所以无可代替!

本期我们详细讲解指针与引用在函数中的应用与对比。

一、关于指针与引用

1、指针

(1)众所周知,指针对应着指针本身(本文简称指针)与指针变量,指针是存指针变量的地址,而指针变量就是指针所指向的值。指针存储于栈区,而指针变量在堆区中开辟空间。

#include <iostream>
using namespace std;
int main()
{
    int a = 10;
    int *p = &a;
    cout<<"输出指针与指针变量的值:"<<endl;
    cout << "指针 :" << p << endl;//输出指针
    cout << "指针变量 :" << *p << endl;//*解引用,输出指针多对应的指针变量

    cout << "关于指针与指针变量的存储区:" << endl;
    cout << "指针 :" << p << endl;//指针变量的地址
    cout << "指针变量 :" << &p << endl;//指针的地址
    system("pause");
    return 0;
}

输出如下:

 (2)指针是变量,需要在内存中开辟一块空间进行存储,可以无初值,如下所示,不会报错:

    int *p;
    cout<<p<<endl;

(3)注意常量指针指针常量的区分,指针常量指指针本身无法改变,而其对应的指针变量可以修改,而常量指针则指针可以修改,而其对应的指针变量无法修改,如下:

2、引用

 (1)基本概念:引用&即别名,是c++的一层语法糖(其实与指针实现机理基本一样),这样可以使用原始数据而不是数据副本,节约大量副本拷贝的时间。

1)声明时必须初始化,否则报错:

2)注意声明&引用符号与取地址&的区别:

    int a=10;
    int &p=a;//此处为声明引用符号
    int *p=&a;//此处为取地址符号

3)不能引用常量,只能引用变量:

(2)本质:常量指针即 int const*(假定为int类型),因此其与初始化对象自始至终都绑定在一起,无法令引用重新绑定到另外一个对象上

(3)右值引用与左值引用:

左值:有存储地址与变量名

右值:既无存储地址又无变量名

int a = 0;  // 在这条语句中,a 是左值,0 是临时值,就是右值。

具体定义如下:详细了解,可看左值引用与右值引用

    int a = 10;
    const int &p = a; //左值引用
    const int &&p1=10;//右值引用

二、引用与指针在函数中如何选择

1、选择基本基本原则

   能用引用尽量用引用,引用优先原则,具体理由如下:

(1)引用必须初始化,否则编译无法通过,而空指针可以编译阶段,在运行阶段往往造成程序的崩溃;

(2)引用不是变量(是另外一个变量的别名),指针是变量,后者需要占用一定的存储空间,当然这是废话了,因为指针所占用那点空间相对于程序来说简直九牛一毛。

2、函数形参中的指针

本文拿交换函数进行举例:

void swap1(int *a,int *b)//交换函数1
{
    int *temp=new int;
    temp=a;
    a=b;
    b=temp;
}

void swap2(int *a,int *b)//交换函数2
{
    int temp;
    temp=*a;
    *a=*b;
    *b=temp;
}
void swap3(int *a,int *b)//交换函数3
{
    int temp;
    temp=*a;
    a=b;
    *b=temp;
}

        实际上,交换成功的只有交换函数2,第一个交换的局部函数里面的地址,其空间是在栈区开辟的,其生存周期仅次于swap1函数,所以实参无任何改变,而第二个交换的是指针变量(在堆区开辟),当然成功。同理,第三个的结果就是a与b的值都一样。输出结果如下:

        因此:指针作为函数的参数,可以修改指针地址指向的值,并且能够正确返回,但是无法通过直接修改指针的地址来改变指针的返回,因为指针参数当中,指针的地址是值传递,无返通过修改值传递得到正确返回。如果想通过修改指针的地址来修改指针的返回结果,需要通过引用传递,则必须将指针改为指针的引用作为函数参数,如void test3(*& p)函数一样。

 通过引用交换地址来达到交换变量值得目的,例子如下:

void swap4(int *&a, int *&b) //交换函数1
{
    int *temp = new int;
    temp = a;
    a = b;
    b = temp;
}

3、函数形参中的引用

        引用用起来显得舒服很多,因为只是一层语法糖,与值传递一样的用法缺达到形参与实参同步的效果:

void swap5(int &a, int &b) //交换函数3
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

        另外,采用引用作为形参避免了临时变量(也就是平时所说的副本)的拷贝,这在面向对象中尤为重要,避免了大量重复的复制拷贝构造函数的时间浪费,如下所示:

//获取大地点之间的距离
double getdistSphe(const GeodeticPoint& A, const GeodeticPoint& B)
{
	auto rad = [](double d) {
		return d * M_PI / 180.0;
	};
	double a = rad(A.m_latitude) - rad(B.m_latitude);
	double b = rad(A.m_longitude) - rad(B.m_longitude);
	double s = 2 * asin(sqrt(pow(sin(a / 2), 2) + cos(rad(A.m_latitude)) * cos(rad(B.m_latitude)) * pow(sin(b / 2), 2)));
	s = s * EARTH_RADIUS;
	return fabs(s);
}

        如果不希望对实参的值进行修改,形参声明为const是一个比较好的习惯,这在很多经典的教材里面被反复提到。

        从上可以看出,选择引用比指针可避免犯大量的错误,可避坑

三、指针与引用作为返回值:

1、指针作为返回值

过于简单不多说,直接上代码,道理全在代码里面:

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;

int *test01()
{
    cout<<"子函数:"<<endl;
    int *a = new int(2);
    cout << a << endl;
    cout<<*a<<endl;
    return a;
}

int main()
{
    int *b=new int(0);
    cout<<"主函数:"<<endl;
    int *a = test01();
    cout << a << endl;
    cout << *a << endl;
    system("pause");
    return 0;
}

输出结果如下:

 注意返回的是一个临时变量(右值),但是不能返回局部变量的地址,否则就会变成空指针,如下:

 本文采用的是G++编译器,返回的是空指针,当然在msvc处理可能与此结果不太一致。

 2、引用作为返回值

(1)首先不能返回局部变量的引用

string& toString(const int& n) {
    string s;
    stringstream ss;
    ss << n;
    ss >> s;
    return s;
}
int main(){
	for (int i = 0; i < 10; ++i) {
        cout << (toString(i)) << endl;
    }
}

        会发现输出的为空字符串,当函数执行完毕,程序将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。

(2)返回引用与返回值得区别:函数返回值时会产生一个临时变量作为函数返回值的副本,而返回引用时不会产生值的副本,返回引用极大提高性能:

struct A
{ 
	int a[10000];
};
A a;
// 值返回
A TestFunc1() 
{ 
	return a;
}
// 引用返回
A& TestFunc2()
{ 
	return a;
}
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
	TestReturnByRefOrValue();
	return 0;
}

结果对比:引用完胜

(3)当引用作为参数时,可返回引用的参数:

 const string &shorterString(const string &s1,const string &s2) 
 {             
         return s1.size()<s2.size()?s1:s2;     
 }

同时,引用作为参数参数可避免了临时变量的拷贝,极大了提高时间性能,这一点上一节以及提到

(4)返回引用可以当做左值使用,最经典的应用就是面向对象中运算符的重载,例如“a=b=c”这种链式编程思想是通过引用来进一步实现的:

	//重载赋值运算符 
	Person& operator=(Person &p)
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//编译器提供的代码是浅拷贝
		//m_Age = p.m_Age;

		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);

		//返回自身
		return *this;
	}

(5)引用可当左值使用,如果不希望改变返回值可以定义为const int &abc(int a, int b, int c, int &result):

#include<iostream>
#include<cstdlib> 
using namespace std;
int &abc(int a, int b, int c, int &result)
{
    result = a + b + c;
    return result;
}

int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    int z;
    abc(a, b, c, z)++; // wrong: returning a const reference
    cout << "z= " << z << endl;
    system("PAUSE");
    return 0;
}

3、返回值选择引用还是指针?

        如果可以选择,则通常更倾向于使用引用而不是指针,因为这样可以降低程序偶然发生内存崩溃的概率。只有在管理那些需要对指针进行操作的对象时(创建、销毁或者添加到一个托管容器中),才会选择使用指针,并且,通常可以将这些例程封装为成员函数。

        但是引用不能为空,如果可能存在空对象时,应考虑使用指针。当一个类中包含引用成员时,这个类就无法使用语言生成的默认构造函数,拷贝构造函数和赋值函数。这是因为引用不能为空(必须初始化),必须在构造函数中为引用成员赋值,语言默认生成的函数不具备这个功能。默认构造函数在C++中时非常重要的概念,特别是在使用STL时,因此为了默认构造函数,必须摒弃引用成员变量。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值