跳到主要内容
C++ 入门:发展历史、命名空间与输入输出详解 | 极客日志
C++ 算法
C++ 入门:发展历史、命名空间与输入输出详解 C++基于 C 语言扩展了面向对象特性,广泛应用于服务器、游戏引擎及底层系统开发。本文梳理了 C++从起源到标准化的发展历程,推荐了 C++ Primer 等经典书籍。重点解析了命名空间解决标识符冲突的原理及嵌套使用方式,并对比了 cin/cout 与 printf/scanf 的差异,介绍了 IO 流性能优化技巧。适合有 C 语言基础的学习者快速过渡。
一、C++的发展历史
1. 发展历史
起源(1979-1983): C++的起源可以追溯到 1979 年,当时 Bjarne Stroustrup 在贝尔实验室从事计算机科学和软件工程的研究工作。面对项目中复杂的软件开发任务,特别是模拟和操作系统的开发工作,他感受到了现有语言(如 C 语言)在表达能力、可维护性和可扩展性方面的不足。1983 年,Bjarne Stroustrup 在 C 语言的基础上添加了面向对象编程的特性,设计出了 C++语言的雏形,此时的 C++已经有了类、封装、继承等核心概念,为后来的面向对象编程奠定了基础。这一年该语言被正式命名为 C++。
标准化(1989-1994): 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): 1997 年 11 月 14 日,联合标准化委员会通过了该标准的最终草案。1998 年,C++的 ANSI/ISO 标准被投入使用。
C++的诞生是为了弥补 C 语言的不足之处!所以部分的 C 语言依然能够在 c++程序中成功运行~
2.C++的版本更新
C++的版本更新基本上在 5 年左右更新一次,大家可以看下图来了解 C++的历代版本与更新内容~
3.C++的参考文档
Reference - C++ Reference
C++ reference - cppreference.com
说明:第一个链接不是 C++官方文档,标准也只更新到 C++11,但是以头文件形式呈现,内容比较易看好懂。后两个链接分别是 C++官方文档的英文版,信息很全,更新到了最新的 C++标准,但是相比第一个不那么易看;这两个文档各有优势,我们结合着使用。
二、C++的学习建议
1.C++的应用领域
C++的应用领域非常广泛,包括服务器端、游戏引擎、机器学习引擎、音视频处理、嵌入式软件、电信设备、金融应用、基础库、操作系统、编译器、基础架构、基础工具、硬件交互等很多方面。
大型系统软件开发: 如编译器、数据库、操作系统、浏览器等等。
音视频处理: 常见的音视频开源库和方案有 FFmpeg、WebRTC、Mediasoup、ijkplayer,音视频开发最主要的技术栈就是 C++。
PC 客户端开发: 一般是开发 Windows 上的桌面软件,比如 WPS 之类的,技术栈的话一般是 C++和 QT,QT 是一个跨平台的 C++图形用户界面程序。
服务端开发: 各种大型应用网络连接的高并发后台服务。这块 Java 也比较多,C++主要用于一些对性能要求比较高的地方。如:游戏服务、流媒体服务、量化高频交易服务等。
游戏引擎开发: 很多游戏引擎就都是使用 C++开发的,游戏开发要掌握 C++基础和数据结构,学习图形学知识,掌握游戏引擎和框架,了解引擎实现,引擎源代码可以学习 UE4、Cocos2d-x 等开源引擎实现。
嵌入式开发: 嵌入式把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,通过软件能够控制这些装置。比如:智能手环、摄像头、扫地机器人、智能音响、门禁系统、车载系统等等,粗略一点,嵌入式开发主要分为嵌入式应用和嵌入式驱动开发。
机器学习引擎: 机器学习底层的很多算法都是用 C++实现的,上层用 python 封装起来。如果你只想准备数据训练模型,那么学会 Python 基本上就够了,如果你想做机器学习系统的开发,那么需要学会 C++。
测试开发 / 测试: 每个公司研发团队,有研发就有测试,测试主要分为测试开发和功能测试,测试开发一般是使用一些测试工具 (selenium、Jmeter 等),设计测试用例,然后写一些脚本进行自动化测试,性能测试等,有些还需要自行开发一些测试用具。功能测试主要是根据产品的功能,设计测试用例,然后手动的方式进行测试。
2.学习书籍推荐
C++ Primer: 主要讲解语法,经典的语法书籍,前后中期都可以看,前期如果自学看可能会有点晦涩难懂,能看懂多少看懂多少,中后期作为语法字典,非常好用。
STL 源码剖析: 主要从底层实现的角度结合 STL 源码,庖丁解牛式剖析 STL 的实现,是侯捷老师的经典之作。可以很好的帮助我们学习别人用语法是如何实现出高效简洁的数据结构和算法代码,如何使用泛型封装等。让我们不再坐井观天,闭门造车,本书课程上一半以后,中后期可以看。
Effective C++: 这本书也是侯捷老师翻译的,本书有一句评价,把 C++程序员分为看过此书的和没看过此书的。本书主要讲了 55 个如何正确高效使用 C++的条款,建议中后期可以看一遍,工作 1-2 年后再看一遍,相信会有不一样的收获。
三、C++的第一个程序 C++兼容 C 语言绝大多数的语法,所以 C 语言实现的 hello world 依旧可以运行,C++中需要把定义文件代码后缀改为.cpp,vs 编译器看到是.cpp 就会调用 C++编译器编译,linux 下要用 g++编译,不再是 gcc。
#include <stdio.h>
int main () {
printf ("Hello World\n" );
return 0 ;
}
然而 C++有自己的一套输入输出设备,所以在 C++中的 hello world 应该是这样:
#include <iostream>
using namespace std;
int main () {
cout << "hello world\n" << endl;
return 0 ;
}
四、命名空间
1.namespace 的价值 在 C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace 关键字的出现就是针对这种问题的。
C 语言项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入 namespace 就是为了更好的解决这样的问题。
#include <stdio.h>
#include <stdlib.h>
int rand = 10 ;
int main () {
printf ("%d" , rand);
return 0 ;
}
因为库函数 stdlib.h 中已经定义了 rand,所以 C 语言就无法运行这串代码了。但在 C++的输入输出中就可以用 namespace 来解决这个问题。
2.namespace 的定义
定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型 等。
namespace 本质是定义出一个域 ,这个域跟全局域各自独立 ,不同的域可以定义同名变量,所以下面的 rand 不在冲突了。
C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/类型出处 (声明或定义) 的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
namespace 只能定义在全局,当然他还可以嵌套定义。
项目工程中多文件中定义的同名 namespace 会认为是同一个 namespace,不会冲突。
C++标准库都放在一个叫 std(standard) 的命名空间中。
1)使用 namespace 来命名空间,以及使用命名空间(详解见注释) #include <stdio.h>
#include <stdlib.h>
namespace A
{
int rand = 10 ;
int ADD (int a,int b) {
return a + b;
}
struct Node {
struct Node * next;
int val;
};
}
int a = 1 ;
int main () {
int a = 0 ;
printf ("%d\n" , a);
printf ("%d\n" , ::a);
printf ("%p\n" , rand);
printf ("%d\n" , A::rand);
printf ("%p\n" , A::ADD);
printf ("%d\n" , A::ADD (1 , 2 ));
struct A ::Node node;
return 0 ;
}
!!!这里需要注意的是命名空间域不会影响变量的生命周期(是一个全局的生命周期)。
2)命名空间的嵌套使用 #include <stdio.h>
#include <stdlib.h>
namespace A {
namespace a {
int rand = 10 ;
int ADD (int a,int b) {
return a + b;
}
}
namespace b {
int rand = 20 ;
int ADD (int a, int b) {
return (a + b) * 10 ;
}
}
}
int main () {
printf ("%d\n" , A::a::rand);
printf ("%d\n" , A::b::rand);
printf ("%d\n" , A::a::ADD (1 , 2 ));
printf ("%d\n" , A::b::ADD (1 , 2 ));
return 0 ;
}
命名空间的嵌套可以嵌套很多层,但是不推荐,因为那样写起来也很不方便~
我们在项目中经常会用到命名空间的嵌套,大家可以看下面的图片来理解一下
3)多文件定义的命名空间问题 多文件中可以定义同名 namespace,他们会默认合并在一起,就像同一个 namespace 一样。下面给大家简单演示一下~
#include <stdio.h>
#include <stdlib.h>
#include "Stack.h"
#include "Queue.h"
int main () {
st::stack s1;
st::Init (&s1);
st::Push (&s1, 1 );
st::Push (&s1, 2 );
st::Pop (&s1);
queue::Queue q1;
queue::Init (&q1);
queue::Push (&q1, 1 );
queue::Push (&q1, 2 );
queue::Pop (&q1);
return 0 ;
}
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
namespace st {
typedef int STDataType;
typedef struct stack {
STDataType* arr;
int top;
int capacity;
}stack;
void Init (stack* ps) ;
void Push (stack* ps, STDataType x) ;
void Pop (stack* ps) ;
}
#include "Stack.h"
namespace st {
void Init (stack* ps) {
}
void Push (stack* ps, STDataType x) {
}
void Pop (stack* ps) {
}
}
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
namespace queue {
typedef int QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode * next;
}QueueNode;
typedef struct Queue {
QueueNode* phead;
QueueNode* ptail;
}Queue;
void Init (Queue* pq) ;
void Push (Queue* pq, QDataType x) ;
void Pop (Queue* pq) ;
}
#include "Queue.h"
namespace queue {
void Init (Queue* pq) {
}
void Push (Queue* pq, QDataType x) {
}
void Pop (Queue* pq) {
}
}
3.namespace 命名空间的使用 编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
指定命名空间访问 ,项目中推荐这种方式。
using 将命名空间中某个成员展开 ,项目中经常访问的不存在冲突的成员推荐这种方式。
展开命名空间中全部成员 ,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。
1)指定命名空间访问 #include <iostream>
#include <algorithm>
int main () {
int a[5 ] = { 4 ,1 ,2 ,5 };
std::sort (a, a + 4 );
return 0 ;
}
2)using 将命名空间中某个成员展开 #include <iostream>
#include <algorithm>
using std::cout;
using std::cin;
int main () {
int a, b;
cin >> a >> b;
cout << a << " " << b << '\n' ;
return 0 ;
}
在这里我们可以看到部分展开了之后 cout 就不需要在前面加东西了,但是 cin 前面还是需要加 std 的。这个展开方式还是推荐在大型项目中使用的~
3)展开命名空间中全部成员 #include <iostream>
#include <algorithm>
using namespace std;
int main () {
int a, b;
cin >> a >> b;
cout << a << " " << b << '\n' ;
return 0 ;
}
这里就是把 std 中的所有成员全部展开,在做算法题或者小程序没有命名冲突的情况下可以使用,而且使用起来比较方便,但是在大型项目中就不推荐使用了~
五、C++输入&输出
是 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,在包含间接包含了。vs 系列编译器是这样的,其他编译器可能会报错。
#include <iostream>
int main () {
int i = 100 ;
double b = 1.11000 ;
std::cout << i << "\n" ;
std::cout << b << "\n" ;
std::cout << "hello world" << std::endl;
std::cout << "hello" << " " << "world" << std::endl;
std::cin >> i >> b;
std::cout << i << " " << b << std::endl;
return 0 ;
}
换行用 '\n' 的效率会比使用 endl 更高,除此以外,cout 和 cin 的效率其实也是不如 printf 和 scanf 的,但是 printf 和 scanf 不能处理类(也就是更复杂的类型),而 C++的 IO 流支持复杂类型的输入输出。在 C++的 IO 流中我们可以通过取消同步流的操作来解决这个问题,代码如下:
#include <iostream>
using namespace std;
int main () {
ios_base::sync_with_stdio (false );
cin.tie (nullptr );
cout.tie (nullptr );
return 0 ;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online