当前位置: 首页 > news >正文

【C++】入门基础知识

河流之所以能够到达目的地,是因为它懂得怎样避开障碍。💓💓💓 

目录

 ✨说在前面

🍋知识点一:C++的发展历史

• 🌰1.C++发展历史 

• 🌰2.C++的迭代与更新

• 🌰3.编程语言排行

• 🌰4.C++在工作领域的应用

• 🌰5.C++参考文档

🍋知识点二:命名空间

• 🌰1.namespace的价值

• 🌰2.namespace的定义

• 🌰3.命名空间的使用

🍋知识点三:输入、输出

🍋知识点四:缺省参数

 • 🌰1.什么是缺省参数?

• 🌰2.函数重载

🍋知识点五:引用

• 🌰1.什么是引用?

• 🌰2.引用的特性

• 🌰3.引用的使用

• 🌰4.const引用

• 🌰5.指针和引用的关系

🍋知识点六:内联函数和空指针

• 🌰1.inline与内联函数

• 🌰2.空指针nullptr

 • ✨SumUp结语


 ✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,上一篇目我们已经完结了初阶数据结构部分的学习,从这一章节开始,我们要开始学习一门新的编程语言—C++。虽然说C++有些难度,但有了之前C语言的基础,相信大家还是非常有把握能够把它学好的。

今天我们将要学习C++的基础知识,包含C++的历史、命名空间、输入输出、缺省参数等等知识,我们今天就解开C++神秘的面纱,详细剖析这一门新的编程语言吧~

  博主主页传送门:愿天垂怜的博客

 

 

🍋知识点一:C++的发展历史

• 🌰1.C++发展历史 

1. 起源与早期发展

  • 起源C++起源于20世纪80年代,由丹麦计算机科学家本贾尼·斯特劳斯特鲁普(Bjarne Stroustrup)在AT&T贝尔实验室发起。当时,斯特劳斯特鲁普为了解决C语言的不足,开始设计一种新的编程语言,这就是C++的雏形。他最初称之为“C with Classes”,意为带有类的C语言。
  • 正式命名1983年,这种语言被正式命名为C++,以表示这是C语言的一种进化和扩展。

2. 标准化进程

  • 标准化开始C++的标准化工作于1989年开始,并成立了一个ANSI和ISO(国际标准化组织)的联合标准化委员会。
  • 标准发布
    • 1994年1月25日,联合标准化委员会提出了第一个标准化草案,并在保持斯特劳斯特鲁普最初定义的所有特征的同时,增加了部分新特征。
    • 1998年,第一个C++标准(C++98)发布,其中包含了许多重要的特性,如类、继承、模板和异常处理等。
    • 随后,C++的标准化工作继续推进,相继发布了C++03、C++11、C++14、C++17和C++20等多个版本。每个版本都引入了新的特性和改进,使得C++成为一门功能丰富的编程语言。

3. 重要里程碑

  • 编译器问世1985年,第一个可用的C++编译器问世,这标志着C++从理论走向了实践。
  • 经典著作斯特劳斯特鲁普在C++的发展过程中撰写了多部经典著作,如《The C++ Programming Language》,这些著作对C++的推广和普及起到了重要作用。
  • 商业应用随着C++的不断发展,越来越多的商业软件开始使用C++编写,如Windows操作系统、浏览器IE等。

4. 未来展望

C++的未来发展方向包括进一步提高语言的性能、简化语法、增加并行和异步编程的支持,并继续引入更多现代编程范式的特性。随着技术的不断进步和需求的不断变化,C++将继续保持其强大的生命力和广泛的应用前景。

5. 关键人物

  • 本贾尼·斯特劳斯特鲁普:作为C++的设计者和实现者,斯特劳斯特鲁普对C++的发展做出了巨大贡献。他的工作不仅推动了C++的标准化进程,还促进了C++在各个领域的应用。

总结:C++的发展历史是一个不断进化和扩展的过程。从最初的“C with Classes”到如今的C++20,C++已经发展成为一门功能强大、应用广泛的编程语言。 

• 🌰2.C++的迭代与更新

时间阶段内容
1998年C++98C++官方的第一个版本,接大多数编译器都支持,得到了国际标准化组织(ISO)和协会认可,以模版方式重写C++的标准库,引入了STL(标准模版库)。
2003年C++03这是C++标准的一个重大修订,主要聚焦了语言的稳定性和兼容性。C++03修正了C++98中的错误和漏洞,同时引入了一些新的特性和功能,如tr1库。
2011年C++11这是一次革命性的更新,增加了大量的新特性和功能,使得C++更像一种新语言,增加了lanmbda、范围for、右值引用和移动语义、边长模板参数、STL容器和核心能指针、标准线程库等。
2014年C++14对C++11的扩展,主要是修复C++11中的漏洞以及改进,比如:泛型的lambda的返回值类型推导,二进制字面常量等。
2017年C++17C++17进一步增强了C++的功能和表达能力。这次更新引入了if constexpr等,同时改进了标准库中的多个组件,如string等。
2020年C++20C++20是C++历史上的有一个重要里程碑。这次更新引入了一系列新特性和改进(Coroutines)、概念(Concepts)、模块化(Modules)等。
2023年C++23C++23是一个小版本更新,进一步完善和改进现有特性,增加了if consteval、falt_map、import std导入标准库等。
2026年C++26制定中...

 

• 🌰3.编程语言排行

  

• 🌰4.C++在工作领域的应用

 C++的应用领域服务器端、游戏(引擎)、机器学习引擎、音视频处理、嵌入式软件、电信设备、金融应用、基础库、操作系统、编译器、基础架构、基础工具、硬件交互等很多方面都有。

1.大型系统软件开发

如编译器、数据库、操作系统、浏览器等等

2.音视频处理

 常见的音视频开源库和方案有FFmpeg、WebRTC、Mediasoup、ijkplayer,音视频开发最主要的技术栈就是C++。

3.PC客户端开发

一般是开发Windows上的桌面软件,比如WPS之类的,技术栈的话一般用C++和QT,QT是一个跨平台的C++图形用户界面(Graphical User Interface,GUI)程序。

4. 服务端开发

各种大型应用网络连接的高并发后台服务。这块Java也比较多,C++主要用于一些对新能要求比较高的地方。如:游戏服务、流媒体服务、量化高频交易服务等。

5. 游戏引擎开发

很多游戏引擎就都是使用C++开发的,游戏开发要掌握C++基础和数据结构,学习图形学知识,掌握游戏引擎和框架,了解引擎实现,引擎源代码可以学习UE4、Cocos2d-x等开源引擎实现。

6. 嵌入式开发

嵌入式把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,通过软件能够控制这些装置。比如:智能手环、摄像头、扫地机器人、智能音响、门禁系统、车载系统等等,粗略一点,嵌入式开发主要分为嵌入式应用和嵌入式驱动开发。

7. 机器学习引擎

机器学习底层的很多算法都是用C++实现的,上层用python封装起来。如果你只想准备数据训练模型,那么学会python基本上就够用了,如果你想做机器学习系统的开发,那么需要学会C++。

8. 测试开发/测试

每个公司研发团队,有研发就有测试,测试主要分为测试开发和功能测试,测试开发一般是使用一些测试工具(selenium、Jmeter等),设计测试用例,然后写一些脚本进行自动化测试,性能测试等,有些还需要自行开发一些测试用具。功能测试主要是根据产品的功能,设计测试用例,然后手动的方式进行测试。

  

   

    

• 🌰5.C++参考文档

https://legacy.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
https://en.cppreference.com/w/

说明:第一个链接不是C++的官方文档,标准也只更新到C++11,但是以头文件的形式呈现,内容比较易看、好懂。后面两个链接分别是C++官方文档的中文版和英文版,信息和全,更新到了最新的C++标准,但是相比第一个不那么易看;几个文档各有优势,可以结合使用。 

 

🍋知识点二:命名空间

• 🌰1.namespace的价值

在C/C++中,变量、函数和后面要学习的类都是大量存在的,这些变量、函数和类的别称都存在于全局作用域中, 可能会导致很多冲突。使用命名空间的目的就是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

C语言项目类似下面这样的命名冲突是普遍存在的问题,C++引入namespace就是为了更好地解决这样的问题。

#include <stdio.h>
#include <stdlib.h>int rand = 10;int main()
{printf("%d\n", rand);//编译报错:error C2365:“rand” : 重定义;以前的定义是“函数”	return 0;
}

由于rand函数实际上在<stdlib,h>头文件中已经被定义为一个函数,用于生成伪随机数,且代码中定义了同名的变量rand,这导致了命名冲突,编译器无法区分到底是想调用rand函数还是引用自己定义的变量rand。

• 🌰2.namespace的定义

🔥定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{ }中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。

🔥namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand不在冲突了。

🔥C++中的域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑,有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量的声明周期。

🔥namespace只能定义在全局,当然他还可以嵌套定义。

🔥项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。

🔥C++标准库都放在一个叫std(standard)的命名空间中。

• 🌰3.命名空间的使用

编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找,所以我们要使用命名空间中定义的变量/函数,有三种方式:

🔥指定命名空间访问,项目中推荐这种方式。

🔥using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。

🔥展开命名空间中的全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。

#include <stdio.h>
#include <stdlib.h>namespace Lilt1
{int a = 10;int b = 20;
}
namespace Lilt2
{int e = 50;namespace Lilt3{int c = 0;int d = 30;}
}
//展开命名空间Lilt1
using namespace Lilt1;
//部分展开Lilt2中的变量e
using Lilt2::e;int main()
{printf("%d\n", a);printf("%d\n", e);printf("%d\n", Lilt2::Lilt3::d);return 0;
}

 

🍋知识点三:输入、输出

 🔥<iostream>是Input Output Stream的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。

🔥std::cin是istream类的对象,它主要面向窄字符(narrow characters(of type char))的标准输入流。

🔥std::endl是一个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区。

🔥<<是流插入运算符,>>是流提取运算符(C语言还用这两个运算符做位运算符左移/右移)。

🔥使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是用过函数重载实现的),其实最重要的是C++的流可以更好的支持自定义类型对象的输入输出。

🔥IO流涉及类和对象、运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++ IO流的用法,之后会有一个站街专门细节讲解IO流库。

🔥cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。

🔥一般日常练习中我们可以用using namespace std,实际项目开发中不建议。

🔥这里我们没有包含<stdio.h>,也可以使用printf和scanf,在包含<iostream>间接包含了。VS系列编译器是这样的,其他编译器不确定。

int main()
{int a = 0;double b = 0.1;char c = 'x';cout << a << " " << b << "\n" << c << "\n" << endl;std::cout << a << " " << b << " " << c << std::endl;int x, y, z = 0;cin >> x >> y >> z;cout << x << " " << y << " " << z << endl;return 0;
}

 注意:

int main()
{// 在io需求比较高的地方,如部分大量输入的竞赛题中,加上以下3行代码// 可以提高C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}

 

🍋知识点四:缺省参数

 • 🌰1.什么是缺省参数?

🔥缺省参数是声明或定义时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参,则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数(有些地方把缺省参数也叫做默认参数)。

🔥全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。

🔥带缺省参数的函数调用,C++规定必须从左往右依次给实参,不能跳跃给实参。

🔥函数声明和定义分离时,缺省参数不能再函数声明和定义中同时出现,规定必须在函数声明给缺省值。

#include <iostream>
using namespace std;void Func(int a = 0)
{cout << a << endl;
}//全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}//半缺省
void Func2(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}int main()
{Func();//没有传参时,使用参数的默认值Func(10);//传参时,使用指定的实参/*Func1();Func1(1);Func1(1, 2);Func1(1, 2, 3);*/Func2(100);Func2(100, 200);Func2(100, 200, 300);return 0;
}

缺省参数的使用案例:

#include <iostream>
using namespace std;#include "Stack.h"//栈的初始化
void STInit(ST* pst, int n)
{assert(pst);pst->a = (STDataType*)malloc(n * sizeof(STDataType));pst->top = pst->capacity = 0;
}int main()
{//确定知道要插入1000个数据,初始化时一并开好,避免扩容ST s2;STInit(&s2, 1000);for (int i = 0; i < 1000; i++){STPush(&s1, i);}return 0;
}

• 🌰2.函数重载

C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++调用就表现出了多态行为,使用更加灵活。C语言是不支持同一作用域中出现同名函数的。

#include <iostream>
using namespace std;//参数类型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}//参数个数不同
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(int a)" << endl;
}//参数顺序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}int main()
{Add(1, 2);Add(1.1, 2.2);f();f(1);f(1, 'x');f('x', 1);return 0;
}

注意下列两种情况:

//是否为函数重载?
//构成,但是f()调用时,会报错,存在歧义,编译器不知道调用谁
namespace bit1
{void f1(){cout << "f1()" << endl;}
}
void f1(int a = 10)
{cout << "void f1(int a = 10)" << endl;
}//是否为函数重载?
//不构成,返回值不能作为重载的条件
void fxx() {}int fxx() { return 0; }

 

🍋知识点五:引用

• 🌰1.什么是引用?

引用不是新定义一个变量,而是给已经存在的变量取了一个别名,编译器不会为应用变量开辟内存空间,它和它应用的变量共用同一块空间。比如:水浒传中李逵,宋江叫“铁牛”,江湖人称“黑旋风”;林冲,外号“豹子头”;

类型& 引用别名 = 引用对象

C++中为了避免引入太多的运算符,会复用C语言的一些符号,比如前面的<<和>>,这里引用也和取地址使用了同一个符号&,大家注意使用方法角度区分就可以。

 

#include <iostream>
using namespace std;int main()
{int a = 0;//引用:b和c是a的别名int& b = a;int& c = a;//也可以给别名b取名,d相当于还是a的别名int& d = b;//这里取地址我们看到的是一样的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;cout << a << endl;//d++全部abc++d++;cout << a << endl;return 0;
}

• 🌰2.引用的特性

🔥引用在定义时必须初始化

#include<iostream>
using namespace std;int main()
{int a = 10;//编译报错:“ra”: 必须初始化引用int& ra;return 0;
}

🔥一个变量可以有多个引用

#include <iostream>
using namespace std;int main()
{int a = 0;//引用:b和c是a的别名int& b = a;int& c = a;//也可以给别名b取名,d相当于还是a的别名int& d = b;return 0;
}

🔥引用一旦引用一个实体,再不能引用其他实体

#include<iostream>
using namespace std;int main()
{int a = 10;int& b = a;int c = 20;/ 这里并非让b引用c,因为C++引用不能改变指向,//这里是⼀个赋值b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}

 

• 🌰3.引用的使用

🔥引用对象在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。

🔥引用传参指针传参的功能是类似的,引用传参相对更方便一些。

🔥引用返回值的场景相对比较复杂,我们再这里简单讲了一下场景,后续内容在类和对象章节细细道来。

🔥引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++中的引用和其他语言的引用(如Java)是由很大区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。

🔥一些主要用C代码实现版本数据结构的教材中,使用C++引用替代指针传参,目的是简化程序,避免复杂的指针,但是很多同学没有学习过引用,导致一头雾水。

示例1:交换函数不需要再传地址
void Swap(int& rx, int& ry)
{int temp = rx;rx = ry;ry = temp;
}int main()
{int x = 0;int y = 2;cout << x << " " << y << endl;Swap(x, y);cout << x << " " << y << endl;return 0;
}

在我们以前学习C语言学习指针的时候,往往会举到这个例子,就是两数互换。因为我们知道,形参是实参的一份临时拷贝,对形参的修改不会影响到实参。所以,当我们想要修改传过去的参数时,应该传参数的地址,利用指针的解引用修改其对应的值。

但是现在,我们有了引用。由于引用的别名和原先的变量都是同一块空间,我们对一个变量的别名进行修改,被引用对象也会被修改。所以,我们直接传变量的别名,就可以实现不需要指针而在改变引用对象时同时改变被引用对象。

示例2:数据结构中用引用代替指针传参

typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, * PNode;//指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
//这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));newnode->val = x;newnode->next = NULL;if (phead == NULL){phead = newnode;}else{//...}
}

和示例1一样,在部分数据结构教材上,使用引用代替指针的写法。但是需要注意,引用不能叠加,没有二级引用这样的写法,所以我们不能写LTNode&& phead,而要写LTNode*& phead。

示例3:引用做返回值

typedef int STDataType;
typedef struct stack
{STDataType* a;int top;int capacity;
}Stack;//弹栈
void STPush(Stack& rs, STDataType x);//初始化栈
void STInit(Stack& rs, int n = 4)
{rs.a = (STDataType*)malloc(n * sizeof(STDataType));rs.top = 0;rs.capacity = n;
}//取栈顶数据
int& STTop(stack& rs)
{assert(rs.top > 0);return rs.a[rs.top];
}int main()
{// 调用全局的stack st1;STInit(st1);STPush(st1, 1);STPush(st1, 2);cout << STTop(st1) << endl;//取得栈顶元素的同时还可以对栈顶元素进行操作STTop(st1) += 10;cout << STTop(st1) << endl;return 0;
}

在示例3中,我们利用STTop函数直接修改了栈顶元素的值。如果我们不用int&做返回值,直接用int做返回值类型,是没有办法实现这个功能的。因为传值返回return语句中返回值会被存储在一个临时变量中,也就是返回它的拷贝,而这个临时变量具有常属性,无法被修改。而我们用引用作为返回值,直接返回栈顶元素的别名,没有中间的临时变量,所以可以直接通过修改别名来修改栈顶元素的值,更加地方便。

 

• 🌰4.const引用

🔥可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但不能放大。

🔥需要注意的是类似 int& rb = a * 3; double d = 12.34 ; int& rd = d; 这样的一些场景下a * 3的结果保存在一个临时对象中,int& rd = d也是类似,在类型转换中会产生临时对象存储中间值,也就是说,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。

🔥所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象。C++中把这个未命名对象叫做临时对象。

#include<iostream>
using namespace std;int main()
{const int a = 10;//int& ra = a;//权限被放大 errorconst int& ra = a;int b = 20;const int& rb = b;//权限被缩小 okreturn 0;
}

上面代码,a是const常量,那么a的别名ra就必须用const int& ra而不能int& ra,否则权限就被放大,这是不行的;相反,b是int类型的变量,可以用int& rb,可以用const int& rb,如果是后者,那么权限就被缩小,这是可以的。

#include<iostream>
using namespace std;int main()
{int a = 10;int b = 20;const int& rc = (a + b);//(a + b)的值存在临时对象中,临时对象具有常属性,必须用const引用double d = 12.34;int i = d;//隐式类型转化也会创建临时变量const int& ri = d;double& rd = d;return 0;
}

上面的代码中,rc引用(a + b)的和结果。对于表达式的结果,会被存储在临时对象中,具有常属性,必须用const引用;临时对象还有一种情况,就是隐式类型转换,在隐式类型转换的同时变量的值也会被存储在临时对象中,也必须用const引用。

 

• 🌰5.指针和引用的关系

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各自有自己的特点,互相不可替代。

🔥语法概念上引用是一个变量的取别名,不开空间;指针是存储一个变量的地址,需要开空间。

🔥引用在定义时必须初始化,指针建议初始化但是在语法上不是必须的。

🔥引用可以直接访问指向对象,指针需要解引用才是访问指向对象。

🔥sizeof中含义不同,引用结果为引用类型的大小,但是指针始终是地址空间所占字节个数(3位平台下占4个字节,64位平台下占8个字节)。

🔥指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来会相对安全一些。

注意:引用和指针在底层是一样的:

#include<iostream>
using namespace std;int main()
{int a = 10;//指针int* pa = &a;*pa = 20;//引用int& ra = a;ra = 30;return 0;
}

转到反汇编: 

可以发现,指针和引用的汇编代码是一样的,他们在底层是相同的。但是我们在使用上不需要考虑底层,明确引用是不开空间,指针是开空间的,不要混淆。 

 

🍋知识点六:内联函数和空指针

• 🌰1.inline与内联函数

🔥用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,就可以提高效率。

🔥inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline使用与频繁调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。

🔥C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂也很容易出错,且不方便调试,C++设计了inline目的就是替代C的宏函数。

🔥inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。

🔥VS编译器debug版本下面默认是不展开inline的,这样方便调试,debug版本想要展开需要设置以下两个地方:

 

为了体现C宏函数的麻烦,以实现ADD加法宏函数为例:

//实现⼀个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))

那么,为什么不能加分号?为什么要加外面的括号?又为什么要加里面的括号?

当我们加了分号,则:

cout << ADD(1, 2) << endl; 会被替换为 cout << ((1) + (2)); << endl; 这会导致输出语句错误,因为多了一个;

如果我们没有加外面的括号,则:

int ret = ADD(1,2) * 5; 就会被替换为 int ret = (1) + (2) * 5; 原本我们希望得到3 * 5的值15,而没有加外面的括号导致2先和5乘,运算顺序发生了改变。

如果我们没有加里面的括号,则:

int ret = ADD(1 & 2, 2 | 3); 就会被替换成int ret = (1 & 2 + 2 | 3); 而 & 和 | 的优先级(位运算符的优先级)是没有 + 来得高的,所以2就会先加2变成4,和我们希望的结果又不一样了。 

由此可见,宏函数是很麻烦的,稍不留神就会导致结果不符合我们的预期,而且还不报错,当代码一长,不好找错误的地方在哪。

为了解决这个问题,我们使用了内联函数:

#include <iostream>
using namespace std;inline int Add(int x, int y)
{int ret = x + y;return ret;
}int main()
{int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;cout << ret << endl;return 0;
}

当我们再ADD前面加上关键字inline,此时ADD函数就是一个内联函数,它和宏类似地,在函数调用的位置直接展开。对于一般的函数,编译后的指令存储在代码段中,而函数中的参数和局部变量存储在函数的栈帧空间。在执行call指令时,会跳转到该函数的地址,执行函数体内部的指令。而对于内联函数,由于直接插入在调用的位置,那么它的参数和变量(如果有的话)将使用调用它的函数的栈帧,不需要创建函数栈帧,也就节省了空间,提升了效率。

不过,展开是由代价的。如果这个函数的代码指定很庞大,或者像递归函数这样的函数,对于这样的函数,如果在每次调用时都展开,那指令的数量将会非常多,这样会使得可执行文件的大小变得很大。所以,inline只是给编译器一个建议,如果编译器觉得这个函数不适合内联,那么它依然会像普通函数那样创建函数栈帧,在链接时进行符号决议和重定位等等操作。

 

• 🌰2.空指针nullptr

C的空指针NULL其实是一个宏,在传统的C头文件(srddef.h)中,可以看到如下代码:

#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL    ((void*)0)#endif
#endif

🔥C++中NULL可能被定义为字面量0,或者C中被定义为无类型指针(void*)的常量。不论采用何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,本想通过f(NULL)调用指针版本的f(int* ptr)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。f((void*)NULL);调用会报错。

🔥C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成其他任意类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式转换成指针类型,而不能被转换成整型。

#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((void*)0);f(nullptr);return 0;
}

上面的代码中,f(0)毫无疑问结果是f(int x);而f(NULL),C++中NULL就是0,所以也是f(int x);而C语言中NULL是((void*)0),虽然是指针类型,但是是void*泛型指针,而不是int*,所以两者都不匹配,编译器会报错。而f(nullptr)中的nullptr可以转换成其他任意类型的指针变量类型,且只能是指针类型,所以它的结果是f(int * ptr)。

C语言和C++在void*能否隐式类型转换上存在差异:

#include <stdio.h>
int main()
{void* p1 = NULL;int* p2 = p1;return 0;
}

 C++是兼容C语言语法的,但是C++在语法上会比C语言严格,例如上面的代码,在C语言文件下是没有问题的,而C++不行。其原因是因为C语言的void*可以隐式类型转换,而C++不行。

 

 • ✨SumUp结语

到这里本篇文章的内容就结束了,本节初步带大家了解了C++这门语言的历史,也给大家介绍了C++的基础知识、语法。这是C++的基础,希望大家能够认真学习。下一章节开始,我们会开始学习C++中的第一大关——类和对象,大家先打好基础,迎接接下来的挑战,希望大家继续捧场~

相关文章:

【C++】入门基础知识

河流之所以能够到达目的地&#xff0c;是因为它懂得怎样避开障碍。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;C的发展历史 • &#x1f330;1.C发展历史 • &#x1f330;2.C的迭代与更新 • &#x1f330;3.编程语言排…...

AI的应用场景和未来展望

AI&#xff08;人工智能&#xff09;的应用场景广泛且多样&#xff0c;已经深入到我们生活的方方面面&#xff0c;成为现代社会不可或缺的一部分。 AI的应用场景 1、通用软件与工具型应用 办公软件&#xff1a;如钉钉、飞书等&#xff0c;通过AI技术提供内容生成与摘要、智能…...

vim、sublime、notepad文本编辑器的使用

VIM&#xff1a; Windows上配置gvim并作为C和C的IDE Windows上配置gvim并作为C和C的IDE | Reasuon sublime notepad...

PyCharm中的外部更改识别:终极解决方案指南

标题&#xff1a;PyCharm中的外部更改识别&#xff1a;终极解决方案指南 引言 PyCharm&#xff0c;作为JetBrains公司开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;以其强大的功能和高效的代码编辑体验而广受开发者喜爱。然而&#xff0c;在开发过程中&#xf…...

Qt——QTCreater ui界面如何统一设置字体

第一步&#xff1a;来到 ui 设计界面&#xff0c;鼠标右键点击 改变样式表 第二步&#xff1a;选择添加字体 第三步&#xff1a;选择字体样式和大小&#xff0c;点击 ok 第四步&#xff1a;点击ok或apply&#xff0c;完成设置...

Linux驱动入门实验班day03-GPIO子系统概述

3.通用框架1——最简单方式1&#xff1a;执行命令cat /sys/kernel/debug/gpio查看串口信息 gpio4对应的下列 方式2&#xff1a; 对于按键GPIO4_14:对应第四组第14个引脚 gpiochip3 ,从96开始&#xff0c; 9614110&#xff1b;...

240803-沉侵式翻译插件配置Ollama的API实现网页及PDF文档的翻译

1. 在插件中点击Options按钮 2. 在开发者模式中启动Enable Beta Testing Features 3 在General中进行设置 ## 4. 在Expand中设置API的URL 5. Qwen&#xff1a;0.5B网页翻译效果 6. Qwen&#xff1a;0.5BPDF翻译效果 7. 参考文献 gemma - 给沉浸式翻译插件配置本地大模型o…...

HTML-08.表单标签

一.表单标签 场景&#xff1a;在网页中主要负责数据采集功能&#xff0c;如注册、登录等数据采集 标签&#xff1a;<form> 表单项&#xff1a;不同类型的input元素、下拉列表、文本域等 <input>:定义表单项。通过type属性控制输入形式 <select>:定义下拉列表…...

SAP ABAP se16n 双击跳转实现

参考老白 SAP小技巧 改造SE16N(九 双击跳转及字段描述优化) (qq.com) se16n 双击跳转实现 我的实现 se38 lse16nlcl 287行 call method cl_gui_control>set_focusexporting control alv_grid. *.....at the moment do detail view on double clickCALL METHOD cl_gu…...

Linux shell编程学习笔记68: curl 命令行网络数据传输工具 选项数量雷人(上)

0 前言 在网络时代&#xff0c;有经常需要在网络上传输数据&#xff0c;时我们需要通过网络下载文件&#xff0c;为了满足这种时代需要&#xff0c;Linux提供了众多网络命令&#xff0c;我们今天先研究curl命令。例如&#xff0c;我们可以使用 curl 从 URL 下载文件&#xff0…...

马尔科夫决策过程

马尔科夫决策过程 贝尔曼方程 贝尔曼方程&#xff08;Bellman Equation&#xff09;是动态规划中的一个核心概念&#xff0c;用于解决最优决策问题。贝尔曼方程通过递归的方式&#xff0c;将问题分解为子问题&#xff0c;从而使得最优策略的求解变得可行。贝尔曼方程广泛应用…...

未知攻焉知防:从攻击者视角看网络安全的“攻守之道”

自首届网络安全攻防实战演练开展以来&#xff0c;这一活动已成为网络安全领域备受关注的大事件。今年&#xff0c;攻防实战演练更上升到了一个全新高度&#xff0c;包括行动任务数量、演练周期时长、攻击强度以及演练类别等&#xff0c;较以往都有极大提升&#xff0c;堪称“史…...

数字孪生赋能智慧城市大脑智建设方案(可编辑65页PPT)

引言&#xff1a;随着科技的飞速发展&#xff0c;智慧城市的建设已成为全球城市发展的新趋势。数字孪生技术作为其中的关键技术之一&#xff0c;正逐步赋能智慧城市大脑的建设&#xff0c;推动城市治理从数字化向智能化、智慧化转型升级。本方案旨在简要介绍数字孪生赋能智慧城…...

c++----内存管理

okk&#xff0c;大家好。我们大家学习了鄙人的前面前面几篇博客&#xff0c;并且还稍微使用了一些c的基础知识。并且我们前面都说过&#xff0c;我们前面学习的知识都说过。我们前面的几篇博客都是我们以后使用c基础。但是我们大家都知道现在代码都关注什么时间啊&#xff0c;内…...

C++——哈希结构

1.unordered系列关联式容器 本节主要介绍unordered_map和unordered_set两个容器&#xff0c;底层使用哈希实现的 unordered_map 1.unordered_map是储存<key,value>键值对的关联式容器&#xff0c;其允许通过key快速查找到对应的value&#xff0c;和map非常相似&#x…...

智能小程序 Ray 开发面板 SDK —— 无线开关一键执行模板教程(一)

1. 准备工作 前提条件 已阅读 Ray 新手村任务&#xff0c;了解 Ray 框架的基础知识已阅读 使用 Ray 开发万能面板&#xff0c;了解 Ray 面板开发的基础知识 构建内容 在此 Codelab 中&#xff0c;您将利用面板小程序开发构建出一个支持一键执行及自动化的无线开关面板&…...

rockDB(1)

文章目录 概述编译rocksdb压缩库 基本接口 小结 概述 RocksDB 是 Facebook 的一个实验项目&#xff0c;目的是希望能开发一套能在服务器压力下&#xff0c;真正发挥高 速存储硬件性能的高效数据库系统。这是一个C库&#xff0c;允许存储任意长度二进制 KV 数据。支持原 子读写…...

[element-ui] 自动获取el-input的焦点

<el-input v-model"filterPlanName" ref"autoFocus" ></el-input>this.$nextTick((_) > {this.$refs.autoFocus.focus(); })参考&#xff1a; [element-ui]自动获取el-input的焦点...

智能闹钟的睡眠评估算法是如何工作的呢

智能闹钟的睡眠评估算法是智能闹钟功能的核心部分&#xff0c;它主要通过以下几个步骤来工作&#xff1a; 一、数据收集 传感器数据&#xff1a;智能闹钟内置多种传感器&#xff0c;如心率传感器、呼吸传感器、体动传感器以及环境传感器&#xff08;如温度、湿度、光线传感器…...

Vue + View-ui-plus Upload实现手动上传

本文实现Vue Upload组件多文件手动上传&#xff0c;支持上传图片&#xff08;image&#xff09;、压缩文件(zip/rar)、表格(excel)、pdf 一、dom结构 <Row><Col :span"19"></Col><Col :span"2"><div class"ivu-btn-uplo…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)

错误一&#xff1a;yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因&#xff0c;后面把yaml.safe_dump直接替换成yaml.dump&#xff0c;确实能保存&#xff0c;但出现乱码&#xff1a; 放弃yaml.dump&#xff0c;又切…...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

Java中栈的多种实现类详解

Java中栈的多种实现类详解&#xff1a;Stack、LinkedList与ArrayDeque全方位对比 前言一、Stack类——Java最早的栈实现1.1 Stack类简介1.2 常用方法1.3 优缺点分析 二、LinkedList类——灵活的双端链表2.1 LinkedList类简介2.2 常用方法2.3 优缺点分析 三、ArrayDeque类——高…...

基于小程序老人监护管理系统源码数据库文档

摘 要 近年来&#xff0c;随着我国人口老龄化问题日益严重&#xff0c;独居和居住养老机构的的老年人数量越来越多。而随着老年人数量的逐步增长&#xff0c;随之而来的是日益突出的老年人问题&#xff0c;尤其是老年人的健康问题&#xff0c;尤其是老年人产生健康问题后&…...