C++基础-命名空间-缺省参数-函数重载-引用-内联-auto-范围for(2)

简介: 4.1 常引用

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;
    //输出的结果都是一个地址

然而他们的底层逻辑是一样的,我们看一下汇编代码:


fc65a0a1d1074ab8aa32fabe9e639f44.png

可以发现,指针和引用的汇编代码完全一样,并没有什么不同,因此我们可以推断,引用就是使用指针实现的。


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使用方法

  1. 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;
  }
}


目录
相关文章
|
1月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
1月前
|
安全 程序员 编译器
【C++】如何巧妙运用C++命名空间:初学者必备指南
【C++】如何巧妙运用C++命名空间:初学者必备指南
|
2月前
|
安全 程序员 编译器
C++ 11新特性之auto和decltype
C++ 11新特性之auto和decltype
38 3
|
1月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
程序员 C++ 容器
C++编程基础:命名空间、输入输出与默认参数
命名空间、输入输出和函数默认参数是C++编程中的基础概念。合理地使用这些特性能够使代码更加清晰、模块化和易于管理。理解并掌握这些基础知识,对于每一个C++程序员来说都是非常重要的。通过上述介绍和示例,希望能够帮助你更好地理解和运用这些C++的基础特性。
40 0
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
29 4
|
7天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
26 4
|
30天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
30天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
30天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1