IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++:函数中的指针与引用 -> 正文阅读

[C++知识库]c++:函数中的指针与引用

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 18:45:50  更:2022-08-19 18:49:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 20:51:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码