[C++]:1.C++基础

C++基础

前言

嗨,我是firdawn,今天我们就要开始学习 C++ 了,本章将介绍C++的基础内容,比如C++的发展和namespace的使用,以及C++的输入输出语法和缺省参数,函数重载等概念,为我们以后的学习打下基础。以下是本章的思维导图,那么让我们开始吧!

一,C++发展历史

C++的起源可以追溯到1979年,当时Bjarne Stroustrup(本贾尼·斯特劳斯特卢普,这个翻译的名字不同的地⽅可能有差异)在⻉尔实验室从事计算机科学和软件⼯程的研究⼯作。⾯对项⽬中复杂的软件开发任务,特别是模拟和操作系统的开发⼯作,他感受到了现有语⾔(如C语⾔)在表达能⼒、可维护性和可扩展性⽅⾯的不⾜。

1983年,Bjarne Stroustrup在C语⾔的基础上添加了⾯向对象编程的特性,设计出了C++语⾔的雏形,此时的C++已经有了类、封装、继承等核⼼概念,为后来的⾯向对象编程奠定了基础。这⼀年该语⾔被正式命名为C++。

在随后的⼏年中,C++在学术界和⼯业界的应⽤逐渐增多。⼀些⼤学和研究所开始将C++作为教学和研究的⾸选语⾔,⽽⼀些公司也开始在产品开发中尝试使⽤C++。这⼀时期,C++的标准库和模板等特性也得到了进⼀步的完善和发展。

C++的标准化⼯作于1989年开始,并成⽴了⼀个ANSI和ISO(International Standards Organization)国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第⼀个标准化草案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的同时,还增加了部分新特征。

在完成C++标准化的第⼀个草案后不久,STL(Standard Template Library)是惠普实验室开发的⼀系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室⼯作时所开发出来的。在通过了标准化第⼀个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,也因此延缓了C++标准化的进程。

1997年11⽉14⽇,联合标准化委员会通过了该标准的最终草案。1998年,C++的ANSI/IS0标准被投入使⽤。

1.1 C++版本更新

版本 时间 特点 内容
C++98 1998 首个正式 ISO 标准,确立 C++ 基础框架,奠定 C++ 语言规范,首次统一语法和标准库 类、继承、多态、模板基础;标准模板库(STL);异常处理;命名空间
C++03 2003 对 C++98 的小幅修订,以修复漏洞为主,提升标准严谨性,保持与 C++98 的兼容性 修正标准中的模糊表述和不一致性(如模板实例化规则);无新增重大特性
C++11 2011 颠覆性更新,引入现代编程特性,被称为 "新 C++" 起点,大幅提升开发效率,减少内存管理错误,让 C++ 适应现代软件开发需求 自动类型推导(auto)、lambda 表达式、decltype(获取表达式类型)、范围 for 循环;智能指针(shared_ptr/ unique_ptr)、移动语义(std::move)、右值引用(&&),减少拷贝开销;nullptr(替代NULL,避免歧义)、constexpr(编译期常量计算)、委托构造函数、继承构造函数等。
C++14 2014 完善 C++11,聚焦易用性提升 泛型 lambda;函数返回类型自动推导;变量模板;std::make_unique
C++17 2017 增强代码简洁性和性能, 引入更多 "零成本抽象" 结构化绑定;if constexpr;并行算法库;折叠表达式;std::optional/std::variant/std::any
C++20 2020 继 C++11 后又一次重大更新,引入底层架构改进 解决头文件依赖问题,简化模板错误信息,支持高效异步编程,扩展算法表达能力。
C++23 2023 优化 C++20 新特性,提升实用性 完善模块系统; 标准化 std::generator;新增 std::flat_map;简化 if/switch 初始化器语法
C++26 2026 制定中

二,参考文档

cplusplus
C++参考文档_中文版
C++参考文档_英文版

说明:第⼀个链接不是C++官⽅⽂档,标准也只更新到C++11,但是以头⽂件形式呈现,内容⽐较易看

好懂。后两个链接分别是C++官⽅⽂档的中⽂版和英⽂版,信息很全,更新到了最新的C++标准,但是

相⽐第⼀个不那么易看;⼏个⽂档各有优势,我们结合着使⽤。

三,C++的重要性

3.1 编程语言排行榜

TIOBE排⾏榜是根据互联⽹上有经验的程序员、课程和第三⽅⼚商的数量,并使⽤搜索引擎(如

Google、Bing、Yahoo!)以及Wikipedia、Amazon、YouTube和Baidu(百度)统计出排名数据,只

是反映某个编程语⾔的热⻔程度,并不能说明⼀⻔编程语⾔好不好,或者⼀⻔语⾔所编写的代码数量

多少。

2026年1⽉TIOBE发布的编程语⾔排⾏榜

3.2 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等),设计测试⽤例,然后写⼀些脚本进⾏⾃动化测试,性能测试等,有些还需要⾃⾏开发⼀些测试⽤具。功能测试主要是根据产品的功能,设计测试⽤例,然后⼿动的⽅式进⾏测试

四,C++学习建议和书籍推荐

4.1 C++学习难度

C++是一门相对难学难精通的语言,相比其他语言,学习难度要高要陡峭一些,这里有C++发展历史和C++语言设计本身的问题。不过,如果我们边学边敲代码,将学会的知识点整理成笔记或者博客的形式,可以帮助我们巩固知识点,方便后期的复习,也可以让面试官知道我们原来是有天天在练习代码,学习C++的知识点,面试官就会对你刮目相看,提高面试官对我们的印象。

4.2 C++学习建议

  1. 把学过的知识点和代码,学会了之后,再自己独立地写一遍,加深自己对C++知识点的印象和理解。
  2. 每天学习完后,将学过的知识点,整理成博客或者笔记,如果时间紧张,至少要将重点章节整理成笔记。

4.3 学习书籍推荐

C++ Primer :主要讲解语法,经典的语法书籍,前后中期都可以看,前期如果⾃学看可能会有点晦涩

难懂,能看懂多少看懂多少,就当预习,学了⽐特课程后,中后期作为语法字典,⾮常好⽤。

STL源码剖析 :主要从底层实现的⻆度结合STL源码,庖丁解⽜式剖析STL的实现,是侯捷⽼师的经典

之作。可以很好的帮助我们学习别⼈⽤语法是如何实现出⾼效简洁的数据结构和算法代码,如何使⽤

泛型封装等。让我们不再坐井观天,闭⻔造⻋,本书课程上⼀半以后,中后期可以看。

Effctive C++ :本书也是侯捷⽼师翻译的,本书有的⼀句评价,把C++程序员分为看过此书的和没看过

此书的。本书主要讲了55个如何正确⾼效使⽤C++的条款,建议中后期可以看⼀遍,⼯作1-2年后再看

⼀遍,相信会有不⼀样的收获。

五,C++的第一个程序

C++兼容C语言的大部分语法,所以C语言实现的helllo world依旧可以运行,C语言实现文件的后缀为 .c ,VS编译器就会使用C语言编译器编译实现文件,而C++实现文件的后缀为**.cpp** ,cpp是C Plus Plus的英文缩写,编译器看到后缀为**.cpp** 就会知道你写的是C++文件,它就会使用C++编译器编译文件;linux编译C语言文件使用gcc编译器,编译C++文件所以g++编译器编译。

因为C++兼容C语言,所以C语言的大部分语法在C++中同样可以使用。不过C++用自己的一套输入输出,严格来说C++版本的Hello World 输出应该是下图这样的

六,命名空间

6.1 namespace的价值

在学习namespace前,我们先来看下图:

在第一幅图中,rand能够被正常打印,但是为什么在第二幅图就错了呢?我们仔细看会发现,第二幅图中包含了 <stdlib.h> 文件,在文件预处理的是时候, <stdlib.h> 里面的函数会被拷贝到当前文件中,而这个头文件里面包含了 rand() 函数,在全局域中,函数名和变量名不能重名,不然会有命名冲突,所以编译器就报错了。其实命名冲突还挺恶心的,比如在公司里,分配了一个项目,一个项目分成很多小组,一个小组有2-3人,我们写的代码写完了以后要和比如合并,如果我们两个都用相同的命名,也会有命名冲突,解决起来就挺麻烦的,这时到底是我用这个名字还是队员用这个命名,是不是就会有冲突了,那我们以后上班是不是要带一把锤子,有命名冲突了就天台相见,谁的武力值高谁就用这个命名。

为了解决命名冲突的问题,我们的祖师爷本贾尼博士就引入了namespace关键字,我们以后在定义变量时就定义namespace命名空间里面。

6.2 namespace的定义

  • namespace定义 :定义命名空间,需要使用namespace关键字,后面跟命名空间的名字,然后打一对 {} 就可以, {} 中就添加命名空间的成员。命名空间中可以定义变量,函数,类型等。

  • namespace解决命名冲突的本质是定义出一个域,这个域跟全局域各自独立,不同的域可以就定义同名变量啦。

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

  • namespace只能定义在全局里,不过它还可以嵌套定义。

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

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

c 复制代码
#include<stdio.h>
#include<stdlib.h>

//namespace的使用
namespace firdawn
{
	int rand = 0;
}
int main()
{
	printf("%d\n", firdawn::rand);
}

当然,在namespce中除了定义变量,还能定义函数和类型。

c 复制代码
//namespace的使用
#include<stdio.h>
#include<stdlib.h>

namespace firdawn
{
	int rand = 0;
	int Add(int x, int y)
	{
		return x + y;
	}

	typedef struct ListNode
	{
		int val;
		struct ListNode* next;
	}LNode;
}

int main()
{
	printf("%d\n", firdawn::rand);
	printf("%d\n", firdawn::Add(1, 2));
	firdawn::LNode p;
}

6.3 namespace的使用

编译查找一个变量的声明或者定义时,默认只会优先在局部查找,然后再在全局里查找,不会到命名空间里面去查找。所以我们要使用命名空间里的成员时一般会用 命名空间名字 + :: ^[1](#命名空间名字 + :: 1 + 命名空间成员变量)^ + 命名空间成员变量 告诉编译器要到哪里查找变量。

我们要使用命名空间中定义的变量,函数时,有三种方式:

  1. 指定命名空间访问,项目中推荐这种方式。
  1. using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐使用这种方式。
c 复制代码
#include<stdio.h>

namespace firdawn
{
	int rand = 10;
	int Add(int x, int y)
	{
		return x + y;
	}

	typedef struct ListNode
	{
		int val;
		struct ListNode* next;
	}LNode;
}
using firdawn::rand;

int main()
{
	
	printf("%d\n", rand);
	printf("%d\n", firdawn::rand);
	
	return 0;
}
  1. 展开命名空间全部成员,项目不推荐,很容易产生命名冲突,日常几百行代码的小练习推荐使用。

七,C++输入和输出

  • iostream 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。
    • std::cin 是 istream 类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输⼊流。
    • std::cout 是 ostream 类的对象,它主要⾯向窄字符的标准输出流。
    • std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
    • <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移)
    • 使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输⼊输出可以⾃动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出。
    • IO流涉及类和对象,运算符重载、继承等很多⾯向对象的知识,这些知识我们还没有讲解,所以这⾥我们只能简单认识⼀下C++ IO流的⽤法,后⾯我们会有专⻔的⼀个章节来细节IO流库。
    • cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们。
    • ⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。
    • 这⾥我们没有包含<stdio.h>,也可以使⽤printf和scanf,在包含 iostream 间接包含了。vs系列编译器是这样的,其他编译器可能会报错。

八,缺省参数

  • 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把缺省参数也叫默认参数)
    • 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
    • 带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
    • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。

九,函数重载

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

十,引用

10.1 引用的概念和定义

  • 引用不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。

类型& 引⽤别名 = 引⽤对象;

  • C++中为了避免引⼊太多的运算符,会复⽤C语⾔的⼀些符号,⽐如前⾯的<< 和 >>,这⾥引⽤也和取地址使⽤了同⼀个符号&,⼤家注意使⽤⽅法⻆度区分就可以。

10.2 引用的特性

  • 引⽤在定义时必须初始化
    • ⼀个变量可以有多个引⽤
    • 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体

10.3 引用的使用

  • 引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被引⽤对象
  • 引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。
  • 引⽤返回值的场景相对⽐较复杂,我们在这⾥简单讲了⼀下场景,还有⼀些内容后续类和对象章节中会继续深⼊讲解。
  • 引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引⽤跟其他语⾔的引⽤(如Java)是有很⼤的区别的,除了⽤法,最⼤的点,C++引⽤定义后不能改变指向,Java的引⽤可以改变指向。
  • ⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序,避开复杂的指针,但是很多同学没学过引⽤,导致⼀头雾⽔。

10.4 const引用

const引用得到的别名,我们只能对其读,不能写。

  • 我们可以引用一个const对象,但是我们必须用const引用。const引用也可以引用普通对象,因为访问权限可以缩小,不能放大。
  • 不过需要注意的是,类似 const int a = 10; int& rb = a*3; double d = 12.34; int& rd = d; 这样一些场景下 a*3的值保存在一个临时变量中,int& rd = d 也是类似,在类型转换中会产生临时变量存储中间值,临时变量具有常量属性,rbrd引用的都是具有常性的临时变量,像int& rb = a*3int& rd = d这样直接引用,如果引用成功,引用得到的别名是可以修改的,这里就触发了权限放大,是不可以的。所以这里我们要对引用对象加const修饰。
  • 所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
  • 值得注意的是引用可以对常量引用,这个功能在以后所以C++中会用起来非常的爽。
c 复制代码
int main()
{
const int a = 10;
// 编译报错:error C2440: "初始化": ⽆法从"const int"转换为"int &"
// 这⾥的引⽤是对a访问权限的放⼤
//int& ra = a;
// 这样才可以
const int& ra = a;
// 编译报错:error C3892: "ra": 不能给常量赋值
//ra++;
// 这⾥的引⽤是对b访问权限的缩⼩
int b = 20;
const int& rb = b;
// 编译报错:error C3892: "rb": 不能给常量赋值
//rb++;
return 0;
}
c 复制代码
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& ra = 30;
// 编译报错: "初始化": ⽆法从"int"转换为"int &"
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:"初始化": ⽆法从"double"转换为"int &"
// int& rd = d;
const int& rd = d;
return 0;
}

10.5 指针和引用的关系

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

  • 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
  • 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  • 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
  • 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
  • sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
  • 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。

十一,inline

  • ⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就需要建⽴栈帧了,就可以提⾼效率。

  • inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。

  • C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调

    试,C++设计了inline⽬的就是替代C的宏函数。

  • vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下

    以下两个地⽅。

  • inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地

    址,链接时会出现报错。

十二,nullptr

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

c 复制代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
  • C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((void*)NULL);调⽤会报错。
  • C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。
c 复制代码
#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(intx),因此与程序的初衷相悖。
	f(NULL);
	f((int*)NULL);
// 编译报错:error C2665: "f": 2 个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);
	f(nullptr);
	return 0;
}

  1. 作用域限定符(Scope Resolvers) ↩︎
相关推荐
三月微暖寻春笋2 小时前
【和春笋一起学C++】(五十九)派生类和基类之间的关系
c++·基类·派生类·关系
阿猿收手吧!2 小时前
【C++】inline变量:全局共享新利器
开发语言·c++
清风玉骨2 小时前
特殊类的创建
c++
郝学胜-神的一滴2 小时前
Linux网络编程中的connect函数:深入探索网络连接的基石
linux·服务器·网络·c++·websocket·程序人生
Cher ~2 小时前
常见C++编译器套件
开发语言·c++
CSDN_RTKLIB3 小时前
target_include_directories对比 PUBLIC / PRIVATE
c++
Titan20243 小时前
搜索二叉树笔记模拟实现
数据结构·c++·笔记·学习
LYOBOYI1233 小时前
qml的布局策略
c++·qt
sycmancia3 小时前
C++进阶02——C++和C中const的区别、三目运算符、引用的本质
c++