【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

目录
相关文章
|
1月前
|
编译器 程序员 C语言
C++函数重载
在实际开发中,有时候我们需要实现几个功能类似的函数,只是有些细节不同。例如希望交换两个变量的值,这两个变量有多种类型,可以是 int、float、char、bool 等,我们需要通过参数把变量的地址传入函数内部。在C语言中,程序员往往需要分别设计出三个不同名的函数,其函数原型与下面类似: void swap1(int *a, int *b); //交换 int 变量的值 void swap2(float *a, float *b); //交换 float 变量的值 void swap3(char *a, char *b); //交换 char 变量的
17 4
C++函数重载
|
14天前
|
程序员 C++ 容器
C++编程基础:命名空间、输入输出与默认参数
命名空间、输入输出和函数默认参数是C++编程中的基础概念。合理地使用这些特性能够使代码更加清晰、模块化和易于管理。理解并掌握这些基础知识,对于每一个C++程序员来说都是非常重要的。通过上述介绍和示例,希望能够帮助你更好地理解和运用这些C++的基础特性。
31 0
|
1月前
|
程序员 C++ 开发者
C++命名空间揭秘:一招解决全局冲突,让你的代码模块化战斗值飙升!
【8月更文挑战第22天】在C++中,命名空间是解决命名冲突的关键机制,它帮助开发者组织代码并提升可维护性。本文通过一个图形库开发案例,展示了如何利用命名空间避免圆形和矩形类间的命名冲突。通过定义和实现这些类,并在主函数中使用命名空间创建对象及调用方法,我们不仅解决了冲突问题,还提高了代码的模块化程度和组织结构。这为实际项目开发提供了宝贵的参考经验。
47 2
|
1月前
|
编译器 Linux C语言
【C++小知识】为什么C语言不支持函数重载,而C++支持
【C++小知识】为什么C语言不支持函数重载,而C++支持
|
24天前
|
C语言 C++
C++(六)Namespace 命名空间
命名空间(Namespace)是为了解决大型项目中命名冲突而引入的机制。在多库集成时,不同类库可能包含同名函数或变量,导致冲突。C++通过语法形式定义了全局无名命名空间,并允许对全局函数和变量进行作用域划分。命名空间支持嵌套与合并,便于协同开发。其使用需谨慎处理同名冲突。
|
24天前
|
C语言 C++
C++(三)内联函数
本文介绍了C++中的内联函数概念及其与宏函数的区别。通过对比宏函数和普通函数,展示了内联函数在提高程序执行效率方面的优势。同时,详细解释了如何在C++中声明内联函数以及其适用场景,并给出了示例代码。内联函数能够减少函数调用开销,但在使用时需谨慎评估其对代码体积的影响。
|
10天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
56 30
|
24天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
1月前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)
|
24天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。