概述
C++ : 入门知识学习 下(引用、内联函数、auto、新式for循环)
引用(别名)
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会给引用变量开辟内存空间,它和它引用的变量共用同一块内存空间,值、地址相同
定义引用的表示方法与定义指针相似,只是用 & 代替了 *,引用(reference)是 C++ 对 C 语言的重要扩充。引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样
格式:类型 &引用变量名 = 已定义过的变量名 (如: int& ra = a ; ra是a的别名)
引用的特点
①引用在定义时必须初始化
②一个变量可取多个引用(别名)
③引用一旦引用一个实体,不能再引用其他实体(确定为一个别名时,不能再为其他别名)
基础引用
void TestRef()
{
int a = 10;
int& ra = a; //定义引用类型
int& rra = ra; //别名取别名
printf("%p %p %pn", &a, &ra, &rra);
}
常引用 (只能缩小,不能放大)
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量;上面语句的权限是“只读”,该条是“可读可写”,权限放大,使用错误
const int& ra = a;
int b = 10;
const int& rb = b; //使用正确;上面语句的权限是“可读可写”,该条是“只读”,权限缩小
int i = 10;
const double& ri = i; //不同类型间的引用(引用的是ri 与 i 之间产生的临时变量,临时变量具有常性,不可更改,前面加了 const 也不再更改,语法上合理)
}
作用
1、做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
Swap ( a, b)
//left是 a的引用;right是 b的引用,left、right做形参,不开辟空间
2、做返回值
- 传值
- 传引用
注意:
如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,则不能用栈上的空间作为引用类型返回。
如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。
传引用比传值的效率高
引用和指针的区别
- 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
- 在底层实现上实际是有空间的,因为引用是按照指针方式来实现的
引用和指针的不同点:
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
内联函数
函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。
一个 C/C++ 程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条,这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如return 0;)结束自己的生命,从而结束整个程序。
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后需要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
特性
- inline是一种以空间换时间的做法,省去调用函数额开销。因此代码很长或者有循环/递归的函数不适宜使用作为内联函数。
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为 inline 的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
- inline 不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
auto
C++98 auto
早在C++98标准中就存在了auto关键字,那时的auto用于声明变量为自动变量,自动变量意为拥有自动的生命期,这是多余的,因为就算不使用auto声明,变量依旧拥有自动的生命期:
int a =10 ; //拥有自动生命期
auto int b = 20 ; //拥有自动生命期
static int c = 30 ; //延长了生命期
C++11 auto
C++98中的auto多余且极少使用,C++11已经删除了这一用法,取而代之的是全新的auto:变量的自动推导变量的类型
auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型,类似的关键字还有decltype。举个例子:
int a = 10;
auto b = a; //自动类型推断,b 为 int类型
cout << typeid(b).name() << endl; //打印出 b 的类型 ,typeid运算符可以输出变量的类型。
程序的运行结果输出了
int
这种用法就类似于 C# 中的 var关键字。auto的自动类型推断发生在编译期,所以使用 auto 不会造成程序运行时效率的降低。而是否会造成编译期的时间消耗,我认为是不会的,在未使用auto时,编译器也需要得知右操作数的类型,再与左操作数的类型进行比较,检查是否可以发生相应的转化,是否需要进行隐式类型转换。
auto的用法
上面举的这个例子很简单,在真正编程的时候也不建议这样来使用auto,直接写出变量的类型更加清晰易懂。
列举auto关键字的正确用法 : 代替冗长复杂、变量使用范围专一的变量声明。
在没有auto的时候,我们操作标准库时经常需要这样:
#include <string>
#include <vector>
int main()
{
std::vector<std::string> vs;
for (std::vector<std::string>::iterator i = vs.begin(); i != vs.end(); i++)
{
//...
}
}
像上面那样看、写代码实在烦得很,使用auto能简化代码:
#include <string>
#include <vector>
int main()
{
std::vector<std::string> vs;
for (auto i = vs.begin(); i != vs.end(); i++)
{
//..
}
}
for 循环中的 i 将在编译时自动推导其类型,不用去定义长长的一串
定义模板函数时,用于声明依赖模板参数的变量类型
template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y)
{
auto v = x*y;
std::cout << v;
}
注意事项
- auto 变量必须在定义时初始化,这类似于const关键字
- 定义在一个auto序列的变量必须始终推导成同一类型。例如:
auto a4 = 10, a5 = 20, a6 = 30; //正确
auto b4 = 10, b5 = 20.0, b6 = 'a'; //错误,没有推导为同一类型
- 如果初始化表达式是引用,则去除引用语义。
int a = 10;
int &b = a;
auto c = b; //c的类型为int而非int&(去除引用)
auto &d = b; //此时c的类型才为int&
c = 100; //a =10;
d = 100; //a =100;
- 如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义
const int a1 = 10;
auto b1= a1; //b1的类型为int而非const int(去除const)
const auto c1 = a1; //此时c1的类型为const int
b1 = 100; //合法
c1 = 100; //非法
- 如果auto关键字带上&号,则不去除const语意
const int a2 = 10;
auto &b2 = a2; //因为auto带上&,故不去除const,b2类型为const int
b2 = 10; //非法
这是因为如何去掉了const,则b2为a2的非const引用,通过b2可以改变a2的值,则显然是不合理的
- 初始化表达式为数组时,auto关键字推导类型为指针
int a3[3] = { 1, 2, 3 };
auto b3 = a3;
cout << typeid(b3).name() << endl;
程序将输出
int *
- 若表达式为数组且auto带上&,则推导类型为数组类型
int a7[3] = { 1, 2, 3 };
auto & b7 = a7;
cout << typeid(b7).name() << endl;
程序输出
int [3]
- 函数或者模板参数不能被声明为auto
void func(auto a) //错误
{
//...
}
- 一定要注意auto并不是一个真正的类型,auto仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid
cout << sizeof(auto) << endl;//错误
cout << typeid(auto).name() << endl;//错误
新式for循环(基于范围的for循环(C++11))
在C++98中如果要遍历一个数组,可以按照以下方式进行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
若循环是一个有范围的集合,用详细代码说明循环的范围是多余的,有时候还容易犯错误。
因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
范围for的使用条件
- for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的
方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
- 迭代的对象要实现 ++ 和 == 的操作
最后
以上就是懵懂金针菇为你收集整理的C++ : 入门知识学习 下(引用、内联函数、auto、新式for循环)C++ : 入门知识学习 下(引用、内联函数、auto、新式for循环)的全部内容,希望文章能够帮你解决C++ : 入门知识学习 下(引用、内联函数、auto、新式for循环)C++ : 入门知识学习 下(引用、内联函数、auto、新式for循环)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复