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

5 篇文章 0 订阅

        为了搞清楚一个变量的生存周期花上大量的时间,为了查找一个反复被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时,因此为了默认构造函数,必须摒弃引用成员变量。

  • 14
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
指向类的成员的指针C++,可以说明指向类的数据成员和成员函数指针。 指向数据成员的指针格式如下: ::* 指向成员函数指针格式如下: (::*)() 例如,设有如下一个类A: class A { public: int fun (int b) { return a*c+b; } A(int i) { a=i; } int c; private: int a; }; 定义一个指向类A的数据成员c的指针pc,其格式如下: int A:: *pc = &A::c; 再定义一个指向类A的成员函数fun的指针pfun,其格式如下: int (A:: *pfun)(int) = A::fun; 由于类不是运行时存在的对象。因此,在使用这类指针时,需要首先指定A类的一个对象,然后,通过对象来引用指针所指向的成员。例如,给pc指针所指向的数据成员c赋值8,可以表示如下: A a; a.*pc = 8; 其,运算符.*是用来对指向类成员的指针来操作该类的对象的。 如果使用指向对象的指针来对指向类成员的指针进行操作时,使用运算符->*。例如: A *p = &a; //a是类A的一个对象,p是指向对象a的指针。 p ->* pc = 8; 让我们再看看指向一般函数指针的定义格式: *() 关于给指向函数指针赋值的格式如下: = 关于在程序,使用指向函数指针调用函数的格式如下: (*)() 如果是指向类的成员函数指针还应加上相应的对象名和对象成员运算符。 下面给出一个使用指向类成员指针的例子: #include class A { public: A(int i) { a=i; } int fun(int b) { return a*c+b; } int c; private: int a; }; void main() { A x(8); //定义类A的一个对象x int A::*pc; //定义一个指向类数据成员的指针pc pc=&A::c; //给指针pc赋值 x.*pc=3; //用指针方式给类成员c赋值为3 int (A::*pfun)(int); //定义一个指向类成员函数指针pfun pfun=A::fun; //给指针pfun赋值 A *p=&x; //定义一个对象指针p,并赋初值为x cout<*pfun)(5)<<endl; //用对象指针调用指向类成员函数指针pfun指向的函数 } 以上程序定义了好几个指针,虽然它们都是指针,但是所指向的对象是不同的。p是指向类的对象;pc是指向类的数据成员;pfun是指向类的成员函数。因此它们的值也是不相同的。 对象指针和对象引用函数的参数 1. 对象指针函数的参数 使用对象指针作为函数参数要经使用对象作函数参数更普遍一些。因为使用对象指针函数参数有如下两点好处: (1) 实现传址调用。可在被调用函数改变调用函数的参数对象的值,实现函数之间的信息传递。 (2) 使用对象指针实参仅将对象的地址值传给形参,而不进行副本的拷贝,这样可以提高运行效率,减少时空开销。 当形参是指向对象指针时,调用函数的对应实参应该是某个对象的地址值,一般使用&后加对象名。下面举一例子说明对象指针函数参数。 #include class M { public: M() { x=y=0; } M(int i, int j) { x=i; y=j; } void copy(M *m); void setxy(int i, int j) { x=i; y=j; } void print() {

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值