C++——初识(2)

一、引言

由于时间关系,上次我们只讨论了C++对C语言的部分改进。今天,让我们继续深入了解这位编程伙伴的其他特性。


二、inline

在学习inline之前,让我们先来回顾一下C语言里面的宏函数,因为这可是C++的祖师爷为了补宏函数的坑设计的。

下面我们来通过代码看看宏函数的坑和正确写法:

cpp 复制代码
#include<iostream>
using namespace std;

// 实现⼀个ADD宏函数的常⻅问题
//#define ADD(int a, int b) return a + b;
//#define ADD(a, b) a + b;
//#define ADD(a, b) (a + b)
// 正确的宏实现
 
#define ADD(a, b) ((a) + (b))
// 为什么不能加分号? 
// 为什么要加外⾯的括号? 
// 为什么要加⾥⾯的括号? 
int main()
{
    int ret = ADD(1, 2);
    cout << ADD(1, 2) << endl;
    cout << ADD(1, 2)*5 << endl;
    int x = 1, y = 2;
    ADD(x & y, x | y);  // -> (x&y+x|y)

    return 0;
}

思考一下我提的三个为什么,平常宏替换是将函数名替换成后面的内容,第一如果加了分号,那cout <<ADD(1,2)<<endl; 不就等于cout <<ADD(1,2);<<endl;了吗;第二个如果不加外面外面的括号,那么大家看看ADD(1,2)*5,宏替换后是否还满足我们所需要的结果;第三个我们不加里面的括号,ADD(x&y,x|y)是否就变成了x&(y+x)|y,因为加法的运算符等级更高。

让我们仔细思考这三个问题:

  1. 如果宏定义末尾加了分号,那么cout << ADD(1,2) << endl;展开后就变成了cout << ADD(1,2); << endl;,这显然会导致语法错误。

  2. 如果不给宏定义加外层括号,例如ADD(1,2)*5展开后就会变成1+2*5,由于运算符优先级问题,结果将不符合预期。

  3. 如果宏参数不加括号,比如ADD(x&y,x|y)展开后会变成x&y + x|y,由于加法运算符优先级高于位运算,实际运算顺序会变成x&(y + x)|y,这将导致计算结果错误。

所以C++在设计的时候就发明了inline去避免宏函数造成的这么坑。

inline:

• 使用inline修饰的函数称为内联函数。在编译时,C++编译器会将内联函数在调用处直接展开,从而避免函数调用时的栈帧开销,提升执行效率。

inline关键字对编译器而言仅是建议性指令,编译器有权决定是否真正展开。不同编译器对内联展开的处理策略各异,因为C++标准未对此作强制规定。内联机制最适合短小且频繁调用的函数,而递归函数或较长的函数即使声明为内联也通常会被编译器忽略。

• C语言通过宏函数实现类似功能(预处理阶段展开),但宏函数编写复杂、易出错且难以调试。C++引入内联函数正是为了取代C的宏函数方案。

• 内联函数不建议将声明与定义分离到不同文件,否则会导致链接错误。由于内联展开后函数地址消失,链接阶段将无法正确解析引用。

额外告诉大家一个看到内联函数是否展开的小妙招哦:

cpp 复制代码
#include<iostream>
using namespace std;

inline int Add(int x, int y)
{
    int ret = x + y;
    ret += 1;
    ret += 1;
    ret += 1;
    return ret;
}
int main()
{
    // 可以通过汇编观察程序是否展开
    // 有call Add语句就是没有展开,没有就是展开了
    int ret = Add(1, 2);
    cout << Add(1, 2) * 5 << endl;

    return 0;
}

三、nullptr

NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:

cpp 复制代码
#ifndef NULL
    #ifdef __cplusplus
        #define NULL    
    #else
        #define NULL    
    #endif
#endif

• 在C++中,NULL可能被定义为字面量0,或在C中被定义为无类型指针(void*)的常量。无论采用哪种定义,使用空指针时都可能出现问题。例如,调用f(NULL)本意是想调用指针版本的f(int*),但由于NULL被定义为0,实际调用了f(int),导致与预期不符。而f((void*)NULL)调用则会报错。

• C++11引入的nullptr是一个特殊关键字,属于特定类型的字面量。它可以隐式转换为任意指针类型,但不会转换为整数类型。使用nullptr定义空指针能有效避免类型转换问题,确保代码意图的准确表达

cpp 复制代码
#include<iostream>
using namespace std;

void f(int x)
{
    cout << "f(int x)" << endl;
}
void f(int* ptr)
{
    cout << "f(int* ptr)" << endl;
}

int main()
{
    f(0);
    // 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。
 
    f(NULL);    //结果为f(int x),与f(0)一样

    //f((int*)NULL);
    // 编译报错:error C2665: "f": 2 个重载中没有⼀个可以转换所有参数类型
 
    // f((void*)NULL);

    f(nullptr);

    return 0;
}

四、总结

总的来说,C++ 这两个新特性,其实都是在给 C 语言的老问题 "打补丁"------ 既保留了 C 语言的高效,又把那些容易踩的坑给填上了。

先说说 inline 内联函数,它就是宏函数的 "升级版"。C 语言的宏函数看着好用,不用函数调用的开销,但写的时候得小心翼翼加各种括号,稍不注意就因为运算符优先级搞出 bug,还没法调试。而 inline 就省心多了,写起来和普通函数一样,编译器会帮我们在调用的地方直接展开,既快又安全,还能支持类型检查和重载。不过也别随便用,要是函数体太长或者有递归,编译器根本不会给你内联,反而白忙活一场。

再看 nullptr,它就是来解决 NULL 的尴尬的。C 语言里 NULL 本质上就是 0,在 C++ 里写函数重载的时候,想传个空指针调用指针版本的函数,结果 NULL 被当成整数,直接调用了 int 版本的,完全违背初衷。而 nullptr 就专门用来表示空指针,只能传给指针类型,再也不会搞混,代码意图也更清晰。现在写 C++ 代码,建议直接把 NULL 换成 nullptr,尤其是用重载和智能指针的时候,能少踩很多坑。

如果大家在 inline 或者 nullptr 上遇到具体问题,比如不知道怎么判断内联有没有生效,或者在老项目里替换 NULL 怕出问题,欢迎在评论区一起讨论~ 编程就是这样,把细节里的坑都踩明白,技术才能慢慢提升,咱们继续在 C++ 的世界里探索吧!其实从宏函数到 inline从 NULLnullptr,能看出来 C++ 的设计思路 ------ 不是要把 C 语言推翻重来,而是在它的基础上,让我们写代码的时候更省心、更安全。

相关推荐
gjxDaniel几秒前
Objective-C编程语言入门与常见问题
开发语言·objective-c
林深现海6 分钟前
Jetson Orin nano/nx刷机后无法打开chrome/firefox浏览器
前端·chrome·firefox
choke23311 分钟前
[特殊字符] Python异常处理
开发语言·python
云中飞鸿11 分钟前
linux中qt安装
开发语言·qt
黄诂多19 分钟前
APP原生与H5互调Bridge技术原理及基础使用
前端
前端市界23 分钟前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
文艺理科生24 分钟前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling25 分钟前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
少控科技25 分钟前
QT第6个程序 - 网页内容摘取
开发语言·qt
darkb1rd25 分钟前
八、PHP SAPI与运行环境差异
开发语言·网络安全·php·webshell