C++基础-命名空间-缺省参数-函数重载-引用-内联-auto-范围for(1):https://developer.aliyun.com/article/1390541
4.1 常引用
void Text() { const int a = 1; //编译会出错,a本身为常量,因为取别名后权限放大 //int& ra = a; const int& ra = a; //因为b为常量,编译的时候同样会出错 //int& b = 10; const int& b = 10; //正确写法 double c = 1.11; //该语句编译时会出错,类型不同 //int& rc = c; const int& rc = c; //涉及到整型提升的问题,临时变量是右值,不可修改,因此const修饰能过 int ii = 1; double dd = ii;//涉及到整型提成,dd实际存储的是临时变量,临时变量具有常性 //double& rdd = ii; //编译不过,因为临时变量具有常性,放大权限了 const double& rdd = ii;//可以编过。 }
强制类型转换、整型提升,产生的都是临时变量,临时变量具有常性;如果在原地改变,有可能空间不够!!!
4.2 引用使用场景
4.2.1 做参数
//引用传值 void Swap(int& a, int& b) { int tem = a; a = b; b = tem; } //传指针 void Swap(int* a, int* b) { int tem = *a; *a = *b; *b = tem; } //对比发现,传引用比传指针更方便,代码量更少,更安全
4.2.2 做返回值
// 引用做返回值 //直接返回别名,可以直接改 int& Func() { static int n = 0; //... n++; return n; } //传指返回 int Func() { int n = 0; //... n++; return n; }
函数返回时,会把当前函数的栈帧清空,返回值存储到一个临时的寄存器中,用来返回给调用者;如果不存到寄存器中,栈帧清空后,才返回到调用者函数,会导致原函数的数据先被OS清空,这样会丢失数据。因此,必须有一个能存储临时变量的寄存器。
使用引用作返回值的前提是:函数返回时,返回的变量不能还给操作系统,否则不能用引用返回,必须使用传值返回
4.2.3 传值和传引用效率分析
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
下面是一组代码用来测试传值和传引用的效率比较:
#include <time.h> struct A { int a[10000]; }; void TestFunc1(A a) {} void TestFunc2(A& a) {} void TestRefAndValue() { A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; } int main() { TestRefAndValue(); //cout << "hello world!" << endl; return 0; }
下面一组代码用来测试返回值和返回引用的效率比较:
#include <time.h> 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; }
4.3 引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和引用的实体共用同一块空间。
int a = 10; int* pa = &a; *pa = 20; int& ra = a; ra = 20; cout << "&a = " << &a << endl; cout << "pa = " << pa << endl; cout << "&ra = " << &ra << endl; //输出的结果都是一个地址
然而他们的底层逻辑是一样的,我们看一下汇编代码:
可以发现,指针和引用的汇编代码完全一样,并没有什么不同,因此我们可以推断,引用就是使用指针实现的。
4.4 指针和引用总结
引用在概念上定义一个变量的别名,指针存储一个变量的地址。
引用在定义时初始化(必须指出引用了谁),指针没有要求。
引用在引用一个实体之后,不能再次改变,指针可以随意指向任意一个实体。
没有NULL引用,但可以有NULL指针。
sizeof中,引用的结果始终是引用类型的大小,但指针始终是地址的字节数。
引用自加即实体+1,指针自加地址偏移一个类型的大小。
有多级指针,但是没有多级引用。指针更复杂
访问实体方式不同:指针需要显式的解引用,引用需要编译器自己处理。
引用比指针使用起来更相对安全。
5. 内联函数
内联函数是用inline关键字修饰的函数,编译时C++会在调用内联函数的地方展开,这样就没有函数调用建立栈帧的开销,可以提升程序的运行效率。用来替代C语言宏的语法
5.1 特性
inline是一种以空间换时间的做法,如果编译器将函数当作内联函数处理,在编译阶段会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。(长函数与递归不适合内联)
inline对于编译器只是一个建议,不同编译器关于inline实现的机制可能不同,一般建议:将函数规模小(函数不是很长)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline不作展开。
inline不建议声明和定义分离,分离会导致链接错误。因为inline在声明处展开,没有函数地址 ,链接就会找不到。因此,声明和定义写一块。
5.2 面试题
宏的优缺点?答:优点:增强代码复用性;提高性能。缺点:不方便调试宏(宏在编译阶段替换掉了);导致代码可维护性差,可读性差,容易误用;无类型安全检查。
C++有哪些技术替代宏?答:常量定义,换用const,enum;缩小函数定义换用内联函数。
6.auto关键字
使用auto关键字可以自动推到变量的类型,不需要再次写类型。
#include<iostream> using namespace std; int main() { int a = 10; char b = 'x'; auto c = a; auto d = b; //auto 自动推测处a, b的类型,并存储它 cout << typeid(c).name() << endl; // int cout << typeid(d).name() << endl; // char }
key:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
6.1 auto使用方法
- auto与引用和指针可以合用
int x = 10; auto a = &x; //类型是 int* auto* b = &x; //只是强调等号右边一定填指针,b的类型也是int* auto& c = x; //给x取别名,c的类型引用
2.在同一行定义多个变量
auto a = 1, b = 2; //声明并定义,能编过 auto c = 1, d = 2.2; //不能编过
当在同一行声明多个变量时,这些变量必须有相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
6.2 auto不能使用的场景
1.auto不能作为函数的参数
//此时会编译失败,auto不能用作形参类型 //因为编译器无法对a的实际类型进行推导 void test(auto a) { ; }
2.auto不能直接用来声明数组
// 声明数组 int a[] = { 1, 2, 3 }; auto b[] = { 1, 2, 3 }; //编译不过
3.auto在实际中最常见的优势用法就是新式for循环,还有lambda表达式等进行配合使用。
7.基于范围for的循环表达式
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号”:”分为两部分: 第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
for(auto 迭代变量 :被迭代的范围 )
使用范例:
int a[] = { 1,2,3,4,5 }; for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { cout << a[i] << " "; // 1 2 3 4 5 } //使用auto推到的范围for //将a中的数据依次赋给data for (auto data : a) { cout << data << " "; // 1 2 3 4 5 } //使用auto& 可以修改a中的数据 //给a的数据取别名 for(auto& data : a) { data++; //此时a中的数据也被改变 } //使用指针不能编译!!! for(auto data : &a) { (*data)++; }
7.1 范围for使用方法
1.for循环迭代的范围必须是确定的!
void Test(int arr[]) { for (auto& data : arr) //不能编译,因为传进来的arr是地址,编译器不能分辨出这个是数组 { cout<<data<<endl; } }