【C++】命名空间&缺省参数&函数重载&引用&内联函数(二)

简介: 【C++】命名空间&缺省参数&函数重载&引用&内联函数

6-1.面试题:

  1. 为什么C语言支持函数重载,而C++支持函数重载?
  2. extern "C'的作用

6-1-1.为什么C语言不支持重载,C++支持?C++是如何支持的?---函数名修饰规则不同

备注:这里由于博主还没有干到LInux,就不能给大佬们演示linux下函数名修饰规则的具体内容了:

C 语言中:

3c6657a4884c442b983901bc66876a9e.pngC++中:

04d5666ff21148eca740b50c7cabdbd1.png

6-1-2.extern "C'的作用

6-1-2-1.什么是中间件程序?为什么会有extern "C"?

在写项目的时候,有的时候会用到中间件程序(配合可执行程序的一些组件):


通常我们就会把它编译成静态库或动态库(比如.dll).


如果这个中间件程序是用C++写的,但是整体的程序时用C语言写的,虽然在编译成二进制的指令的时候,C和C语言都没太大差异(因为此时已经经历了各自编译器的编译),但是由于C语言和C++的函数名修饰规则,整体程序在找中间件程序(组件)中的函数的时候就会表示找不到.这时extern "C"的作用就凸显出来了.


6-1-2-2.extern "C"的作用和为什么可以通过extern "C" 解决这个问题?


extern "C" 的作用:让C++用C的函数名规则去找函数地址.


基石:C++兼容C的语法,C++知道C语言的函数名规则,所以在有C和C++的函数名规则冲突的时候,在C++程序中使用extern "C" +函数声明  ,就可以解决这个问题.


6-1-2-3.extern "C"的使用场景举例:


下面以谷歌自己用C++写的tcmalloc代替mallc ,然后写成了一个中间件程序,后来一个C语言程序想用这个中间件程序代替mallc时他遇到的问题和解决办法:


baf3fe445fe24b42bd1ba43b39a29a3f.png


变式: 如果加完了extern "C",有同时有整体C++程序想使用这个被extern "C"修饰过了的中间件.这就可以将这个整体C++程序前加上extern "C".


7.引用

7-1.引用的基本使用(reference)

#include<iostream>
int main()
{
  int a = 10;
  int& b = a;//b是a的别名,b是a的引用
  printf("%d\n", b);
  b = 100;
  printf("%d\n", a);
}

fd4541528af14855ab9e202339ae12e9.png

注意:int& b=a;是取别名

而int* b=&a;是取地址

#include<iostream>
using namespace std;
void Swap(int* m, int* n)
{
  int temp = *m;
  *m = *n;
  *n = temp;
}
void Swap(int& m, int& n)
{
  int temp = m;
  m = n;
  n = temp;
}
int main()
{
  int a = 10;
  int b = 20;
  //传地址交换
  Swap(&a, &b);
  printf("a=%d\tb=%d\n", a, b);
  //传引用交换
  Swap(a, b);
  printf("a=%d\tb=%d\n", a, b);
  return 0;
}

6f1e25ac7fed4ca2ba6901d325cc3769.png


7-2.引用的特性 :

  1. 一个变量可以有多个别名
  2. 引用必须初始化(但是指针没有初始化的规定)
  3. 引用一旦引用了一个实体后,就不能再引用其他实体
int main()
{
  int a = 10;
  //一个变量可以有多个别名b,c,d
  int& b = a;
  int& c = a;
  int& d = a;
  //引用必须初始化
  int& e = a;
  //不是让e变成r的别名,而是把r赋值给e
  int r = 20;
  e = r;
  printf("%d\n", e);//20
  return 0;
}

7-3.常引用

int main()
{
    //const权限
    const int a = 10;//这里的a是可读不可写
  int& ra = a;//错,权限的放大不允许
  //错在把可读不可写的变量a给一个可读可写的引用
  const int& ra = a;//对
  int b = 10;//-可读可写
  const int& rb = b;//对,权限的缩小允许-可读不可写
  return 0;
}

只要是有类型差异在赋值转换时都会产生临时变量

转换:转换的是中间的临时变量,而不是c

  //隐式转换(权限的应用)
  int c = 10;
  double d = 1.1;
  d = c;//对,c语言隐式类型转换,但还是一样是有临时变量(double)类型
  //double& rc = c;//错,错因是因为是const double 类型的临时变量给了double类型的变量
  const double& rc = c;//对

8f3a5f2f75d34144baf15221c203f25c.png

备注:这里rc引用的不是C,因为类型差异(字节都不一样),rc引用的其实时中间的那个临时变量.

7-4.引用的场景

7-4-1.作参数

a4e59a225a4c4b049aef53d6e28f6bdb.png

7-4-2做返回值(传引用返回)

先看看之前我们学过的传值返回:

传值返回返回的是对象c的拷贝

0c0d0ec3c9564e6e8769081c92a3f8d2.png

这里说明了实际上是函数返回值是通过产生一个临时变量(const修饰)来临时保存,然后赋值给ret。

传引用返回:

传引用返回的是对象c的引用

8d9623811c764c82a33bc5fc4819657d.png

这里返回值是int&,也就是返回的是c的引用,也就是c的别名,然后c的别名又是ret的别名

函数栈帧问题:

int& Add(int a, int b)
{
  int c = a + b;
  return c;
}
int main()
{
  const int& ret = Add(1, 2);
  Add(5, 7);
  cout << ret << endl;//12
  return 0;
}


下面即是在main函数里没有用ret接收Add(5,7)的返回值,ret还是被改为了12,那是对因为ret是栈上已经销毁的变量c的引用 。


08846b80403842119757fd534e586ecd.png


但是如果是传值返回:调用了Add(5,7),还是3


1c3004a761694e5481308048d5013710.png


或者把c定义在static,静态常量区上:


655c192a3660453f80773ab29ce34532.png


越界不一定报错:


1.越界读基本不报错,因为编译器检查不出来


2.越界写,可能报错,而且是抽查,就像查酒驾,一般是查可能性大的地界查--抽查


传引用返回的优点:


因为传值返回传的是对象的拷贝,但是传引用返回是返回的是对象的别名,可以提高效率,这和传值调用和传址调用很像。


指针和引用的异同:

int main()
{
  int a = 10;
  //语法上,这里是给a起了一个别名,而是新定义了一个符号,并没有额外开空间
  int& ra = a;
  ra = 20;
  //语法上,这里是定义了内存是4个字节的变量存放a的地址
  int* pa = &a;
  *pa = 20;
  return 0;
}

实际从汇编实现的角度,引用的本质类似指针取地址的方式实现的(语法层和底层是隔离开的)---了解即可

指针和引用的不同点::

内存开辟角度(概念上)

初始化角度

实体对象更改角度

空指针角度

多级指针角度

引用更安全角度

8.内联函数

由C语言引入:

//C语言为了避免小函数开辟函数栈帧的开销--->提供了宏,预处理阶段展开
#define Add(x,y)  ((x)+(y))
int main()
{
  int x = 1, y = 2;
  int ret = Add(1, 2);
  printf("%d\n", ret);
  return 0;
}

C++推荐使用频繁的小函数,定义成inline函数,没有函数的开销,只是在调用的时候展开

内联函数:这里结合了宏没有函数开销的优点,同时又丢弃了宏复杂和不支持调试的缺点。

inline int Add(int a, int b)
{
  return a + b;
}
int main()
{
  int x = 1, y = 2;
  int ret = Add(1, 2);
  printf("%d\n", ret);
  return 0;
}

为什么不是将所有的函数定义成内联函数?(内联的缺陷)


1.因为内联函数的本质是通过通过牺牲展开函数,增加主函数代码量(指令变多,导致编译出来的程序变大,备注:指令变多不一定耗时长)来提高效率,而减少函数调用的开销,从而提高效率的。------>空间换时间所以适合将那些函数内部代码量比较少且频繁被调用的的函数定义成内联。当把大函数定义成内联时,编译器直接不搭理你的定义内联。备注:当调用1000次时,内联展开和调用函数的指令数是截然不同的。


2.内联不建议声明和定义分离,因为内联函数没有地址(直接展开了),会导致链接时找不到。


680a7d7af2f3402ab8f20b9382360ca8.png

目录
相关文章
|
2月前
|
程序员 C++
C++中的函数重载有什么作用
【10月更文挑战第19天】C++中的函数重载有什么作用
23 3
|
2月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
安全 程序员 编译器
【C++】如何巧妙运用C++命名空间:初学者必备指南
【C++】如何巧妙运用C++命名空间:初学者必备指南
|
2月前
|
编译器 程序员 C++
C++中的函数重载是什么
【10月更文挑战第19天】C++中的函数重载是什么
34 0
|
2月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
自然语言处理 编译器 Linux
【C++】巧用缺省参数与函数重载:提升编程效率的秘密武器
【C++】巧用缺省参数与函数重载:提升编程效率的秘密武器
|
20天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
30 2
|
26天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
67 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
70 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
81 4