【C++】-- 命名空间、函数重载、内联函数(三)

简介: 【C++】-- 命名空间、函数重载、内联函数

3.C++中将一个函数按照C的风格来编译

使用extern "C"可以在C++文件中引用c文件的函数

f.h文件

1. #pragma once
2. #define  _CRT_SECURE_NO_WARNINGS  1
3. #include<stdio.h>
4. 
5. int add(int a, int b);

f.c文件

1. #include "f.h"
2. 
3. int add(int a, int b)
4. {
5.  return a + b;
6. }

main.cpp

1. #include "f.h"
2. 
3. int main()
4. {
5.  int ret = add(2, 3);
6.     printf("%d",ret);
7. 
8.  return 0;
9. }

程序报错,这是因为当c++调用add函数时,add函数被编译器编译后,函数签名变成了"?add@@YAHHH@Z"链接时,main函数却找不到这个符号。  

为了能够让c++调用c文件中实现的函数,需要给该函数加上extern "C",修改f.h文件如下:

1. #pragma once
2. #define  _CRT_SECURE_NO_WARNINGS  1
3. #include<stdio.h>
4. 
5. //写法一
6. extern "C" int add(int a, int b);
7. 
8. //写法二,能针对多行声明:可将要按照c风格进行编译的所有函数声明全部放在{ }中
9. //#ifdef __cplusplus
10. //extern "C"{
11. //#endif
12. //int add(int a, int b);
13. //#ifdef __cplusplus
14. //};
15. //#endif

编译成功

为什么加上extern "C"后,就能执行成功呢?

这是由于windows下的编译器会在c语言符号(变量和函数)前加上“_”,即add函数被编译后的符号为_add。因为c语言如果只有函数声明而缺少函数定义,会报缺少"_add"符号:

f.h

1. #pragma once
2. #define  _CRT_SECURE_NO_WARNINGS  1
3. #include<stdio.h>
4. 
5. //extern "C" int add(int a, int b);
6. #ifdef __cplusplus
7.  extern "C"
8. #endif
9. int add(int a, int b);

f.c

1. #include "f.h"
2. 
3. //int add(int a, int b)
4. //{
5. // return a + b;
6. //}

main.c

1. #include "f.h"
2. 
3. int main()
4. {
5.  printf("%d", add(2, 3));
6.  return 0;
7. }

在VS下,c++文件调用c实现的函数时,编译时,c函数add编译后的符号为"_add"。而在c++的main中,add编译后的符号为"?add@@YAHHH@Z"。两个符号不一致,导致链接失败。而当add在extern "C"中声明后,c++的main中就会将add编译成符号"_add",符号一致,链接成功。


五、内联函数

1.定义

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,因此,内联函数能够提升程序运行的效率。

1. #include<iostream>
2. 
3. int Add(int a, int b)
4. {
5.  int c = a + b;
6.  return c;
7. }
8. 
9. int main()
10. {
11.   int ret = 0;
12.   ret = Add(1, 2);
13. 
14.   return 0;
15. }

VS环境下,F10-调试-窗口-反汇编,发现会call Add

加上inline关键字后,在release模式和debug模式下查看是否存在call Add语句

(1)release模式下,直接查看编译器生成的汇编代码中是否存在call Add

(2)debug模式下,编译器默认不会对代码进行优化,需要对编译器进行设置,否则不会展开:调试-调试属性-配置属性-C/C++-常规-调试信息格式-程序数据库(/Zi)

调试-调试属性-配置属性-C/C++-常规-内联函数扩展-只适用于_inline(/Ob1)

查看反汇编结果,没有call Add,直接使用了add指令:

2. 特性

(1)inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。

(2)inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。

(3)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

f.c

1. #pragma once
2. #include <iostream>
3. using namespace std;
4. inline void f(int i);

f.cpp

1. #include "f.h"
2. 
3. void f(int i)
4. {
5.  cout << i << endl;
6. }

main.cpp

1. #include "f.h"
2. 
3. int main()
4. {
5.  f(10);
6.  return 0;
7. }

编译报错,链接错误,如下所示。将inline修饰的函数声明和定义放在.h或源文件就可以了。

3.为什么不能每个函数都使用inline

假如有一个函数,进行编译汇编后有100条指令,如果有10个地方调用

(1)该函数不加inline,建立栈帧,总计100 + 10 = 110条指令

(2)该函数加inline,不建立栈帧,每个地方都展开,总计100 * 10 = 1000条指令

从110条指令变成1000条指令,虽然建立栈帧不一定比不建立栈帧慢,但是编译出来的可执行程序变大,安装软件的人体验变差,执行程序内存消耗变多。

4.inline和宏

C语言为了避免小函数建帧的消耗,提供宏函数支持,在预处理阶段展开。既然C语言已经解决了,为什么C++还要提供inline函数?

因为宏有以下缺点:

(1)不支持调试(编译阶段进行了替换)

(2)宏函数语法复杂,容易出错

(3)没有类型安全检查

c++使用三种法师代替宏:枚举、const常量定义、inline内联函数。


六、指针空值nullptr

NULL实际是一个宏,NULL的定义:

NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。但对于如下代码:

1. void f(int)
2. {
3.     cout<<"f(int)"<<endl;
4. }
5. 
6. void f(int*)
7. {
8.     cout<<"f(int*)"<<endl;
9. }
10. 
11. int main()
12. {
13.     f(NULL);
14.     f((int*)NULL);
15. 
16.     return 0;
17. }

在c++中,NULL被定义为0,想用f(NULL)用空指针NULL作为参数,但是根据打印结果,发现 f(NULL)调用的是参数为int的f函数:

这是因为C++中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。 为此,C++11新增了关键字nullptr,用于表示空指针。为向后兼容,C++11仍然可以使用0来表示空指针,因此表达式nullptr=0为true,但使用nullptr提供了更高的类型安全。

注意:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2.nullptr是指针类型,不能转化为整形类型,可以隐式转换为任意类型的指针,也可以隐式转换为bool类型代表false。

3. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

4. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

相关文章
|
11天前
|
C++
C++命名空间(namespace)的使用
C++命名空间(namespace)的使用
|
13天前
|
存储 编译器 C语言
【C++入门】—— C++入门 (下)_内联函数
【C++入门】—— C++入门 (下)_内联函数
13 2
|
13天前
|
人工智能 安全 编译器
【C++入门】—— C++入门 (上)_命名空间
【C++入门】—— C++入门 (上)_命名空间
16 2
|
2天前
|
Unix 编译器 C语言
【C++航海王:追寻罗杰的编程之路】关键字、命名空间、输入输出、缺省、重载汇总
【C++航海王:追寻罗杰的编程之路】关键字、命名空间、输入输出、缺省、重载汇总
5 0
|
3天前
|
存储 安全 编译器
【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字
【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字
10 0
|
3天前
|
编译器 C语言 C++
【C++】:C++关键字,命名空间,输入&输出,缺省参数
【C++】:C++关键字,命名空间,输入&输出,缺省参数
10 0
|
9天前
|
编译器 C++
C++对C的改进和拓展\域解析符、形参默认值、函数重载
C++对C的改进和拓展\域解析符、形参默认值、函数重载
7 0
|
3天前
|
安全 编译器 C++
【C++】学习笔记——类和对象_5
【C++】学习笔记——类和对象_5
17 9
|
2天前
|
存储 编译器 C语言
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
8 2
|
2天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
2 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)