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的内容下篇文章还会继续学习,引用的知识环环相扣,对于一些逻辑的理解至关重要,望反复练习,加强推理能力!!!
,