C++学习第四课:引用与auto关键字

C++学习

C++学习第一课:C++学习须知
C++学习第二课:命名空间域
C++学习第三课:缺省函数与函数重载
C++学习第四课:引用与auto关键字



前言

    本篇文章在上一篇的基础上对引用做了更加细致的介绍,内容较为烧脑,对于知识的掌握个人认为要求较高,环环相扣,除引用之外,还涉及了部分auto内容,仅作了解,下篇文章将对auto做更细致的学习!

一、引用的复习

1. 引用的主要价值:

(1)代替指针(但c++的引用无法完全代替)做输出型参数(对比以前的输入型参数)/提高效率(引用不用额外开辟空间)
(2)引用做返回值.作用一:提高效率,减少拷贝

引用返回

int& Count()
{
    static int n=0;
    n++;
    return n;
}
int main()
{
    int ret = Count();
    cout << ret <<endl;
    return 0;
}

传值返回

在这里插入代码片

注意:传值返回中,函数返回值并不是直接返回的,而是先生成一个临时变量(也可能用寄存器代替),将返回值存放在其中,再传回去做返回值

提问1:为什么要有这个临时变量呢

答:以传值返回代码为例,当count函数执行完毕,n就被销毁了,无法返回,因此需要创建一个临时变量做存储,然后进行两次拷贝

注意:销毁并不是指将空间销毁,而是归还空间的使用权,导致空间原来的数据没保障

提问2:是否生成临时变量由谁决定

答:由int main中的int 这种返回值类型决定

提问3:引用返回还要不要生成临时变量

答:不。因此引用返回可以减少拷贝,提高效率,特别是在大对象的时候(即返回值比较大的时候)

提问4:是否任何情况都可以用引用返回呢

答:不是,静态值才可以,否则有隐患

静态变量举例:static修饰

注意:static关键字

在函数内部使用static修饰的变量称为局部静态变量。

与普通局部变量不同,局部静态在函数结束之后不会被销毁,而是保持其值。

int& Count()
{
    static int n=0;
    n++;
    return n;
}
int main()
{
    int ret = Count();
    cout << ret <<endl;
    return 0;
}

有隐患的引用返回举例

int& Count()
{
    int n=0;
    n++;
    return n;
}
int main()
{
    int ret = Count();
    cout << ret <<endl;
    return 0;
}

注意:这里打印的ret的值是不确定的,取决与count函数结束后,n原来空间的值有没有被清理

int& Count()
{
    satic int n=0;
    n++;
    return n;
}
int main()
{
    int& ret = Count();
    cout << ret <<endl;
    return 0;

注意:这里ret是n的别名,隐患就更大了,本来就看你刚销毁那一会有没有被清理,现在你ret也指向那个空间了,随时被清理就随时变了
(3)引用做返回值二:修改返回值(对数据的增删查改很好用)

c语言普通返回实现

struct SeqList
{
    int a[100];
    size_t size;
};
void SLModify(SeqList* ps,int pos,int x)
{
    assert(pos<100 && pos>=0);
    ps->a[pos]=x;
}
int SLGet(SeqList* ps,int pos)
{
    assert(pos<100 && pos>=0);
    return ps->a[pos];
}
int main()
{
    SeqList s;
    SLModify(&s,0,1);//将第0个位置的值改成1
    SLGet(&s,0);//得到第0个位置的值
    int ret = SLGet(&s,0);
    SLModify(&s,0,ret+5);//对第0个位置的值+5
}

c++引用返回实现

struct SeqList
{
    int a[100];
    size_t size;
};
int& SLAt(SeqList* ps,int pos)
{
    assert(pos<100 && pos>=0);
    return ps->a[pos];
}
int main()
{
    SeqList s;
    SLAt(&s,0)=1;//将第0个位置的值改成1
    cout<<SLAt(&s,0)<<endl;//得到第0个位置的值
    SLAt(&s,0)+=5;//对第0个位置的值+5
}

注意:发现引用做返回值,在以数列作为返回值的时候特别好用,因为数列是外面传进来的,函数运行完毕之后数列不会被销毁

2. 引用与权限

(1)引用过程中权限不能放大(下附错误代码举例)

注意:const关键字
(1)const是C语言中的一个关键字,是定义只读变量的关键字。但是一个变量经过const的修饰后,不仅拥有常量的属性,又有变量的属性,所以又说const是定义常变量的关键字。
(2)const成员变量只能调用const成员函数,不能调用非const成员函数

int main()
{
    const int a =0 ;
    int& b=a;
}

注:下面这种赋值拷贝就没关系

int main()
{
    const int a =0 ;
    int b=a;
}

(2)引用过程中权限可以平移或者缩小

int x=0;
const int& y = x;

回顾点1:有一说一这个地方不是很懂为什么,有大佬看到这的话可以帮我解释一下就太感谢了,或者等我日后明白了再回来补充解释吧

(3)临时变具有常性

double dd = 1.11;
int ii = dd;
int& rii = dd;//这里是不行的

分析:类型转换时的赋值就像函数返回值一样,会有一个中间临时变量,然而临时变量具有常性(相当于被const修饰),因此这里就发生了权限放大

对于类型不同变量转换时产生临时变量的举例

int main()
{
    int i = 1;
    double j = 1.1;
    if(j>i)
    {
        cout<<"yes"<<endl;
    }
    return 0;
}

***分析:***这里i和j是不同类型的,做比较的时候会对范围小的
类型的变量做一个提升,即i,不过并不是将i提升成1.0,而是产生一个临时变量1.0和j去比较

对返回值产生临时变量的更多举例

int func()
{
    static int x = 0;
    return x;
}
    int main()
{
    int& ret = func();//这里也不可以,理由一样,权限放大
}
int func()
{
    static int x = 0;
    return x;
}
    int main()
{
    const int& ret = func();//这里可以,属于权限平移
}
int& func()
{
    static int x = 0;
    return x;//这里返回值是没有临时变量的,返回的是x的别名
}
    int main()
{
    const int& ret = func();//因此这里是欧克的,属于权限缩小
}

3. 引用到底有没有开空间

语法层面

(1)引用只是别名,不开空间
(2)指针要开辟空间存储地址

底层层面

答:都有开空间的,因为引用是按照指针方式来实现的

4. 引用与指针的异同点

(1)引用概念上定义一个变量的别名,指针存储一个变量地址。
(2)引用在定义时必须初始化,指针没有要求
(3)引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
(4)没有NULL引用,但有NULL指针
(5)在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
(6)引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
(7)有多级指针,但是没有多级引用
(8)访问实体方式不同,指针需要显式解引用,引用编译器自己处理
(9)引用比指针使用起来相对更安全

二、auto关键字

1. auto的作用

(1)自动推导变量的类型

c++中打印类型的方法

cout<<typeid(d).name()<<endl;

2. auto的使用场景

(1)类型名很长的时候:例如,迭代器

#include <string>
#include <map>
int main()
{
 std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", 
"橙子" }, 
   {"pear","梨"} };
 std::map<std::string, std::string>::iterator it = m.begin();
 while (it != m.end())
 {
 }
 return 0;
}

这里其实也可以用typedef简化代码

#include <string>
#include <map>
typedef std::map<std::string, std::string> Map;
int main()
{
 Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };
 Map::iterator it = m.begin();
 while (it != m.end())
 {
 //....
 }
 return 0;
}

但是使用typedef会有新的问题

typedef char* pstring;
int main()
{
 const pstring p1;    // 编译成功还是失败?
 const pstring* p2;   // 编译成功还是失败?
 return 0;
}

(2)含义不明确导致容易出错:例如,范围for

语法糖:范围for

for(auto e : arr)
for(auto& e : arr)
//将arr中的元素一个一个赋值给e,自动识别结束
//注:下面这样也可以
for(int e : arr)
//但是这种就只能用在int类型元素的数组
//注:效率不会降低

总结

截止今天,对引用的学习大概有70%,剩下的知识需要日后结合其他的再进行学习,此外,关于auto的内容下篇文章还会继续学习,引用的知识环环相扣,对于一些逻辑的理解至关重要,望反复练习,加强推理能力!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值