前言
C++ 是一门高效、灵活且功能强大的通用编程语言,由 Bjarne Stroustrup 于 1979 年在贝尔实验室开发。它通常被视为 C 语言的延伸,在 C 语言的基础上增加了面向对象编程(OOP)和泛型编程的支持。C++ 是一门'难学但上限极高'的语言,如果追求极致的程序运行效率,或者需要深入理解计算机底层运作原理,C++ 是必修课。
一、C 与 C++ 程序对比
C 语言输出 Hello World:
#include <stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
C++ 输出 Hello World:
#include <iostream>
using namespace std;
int main() {
cout << "Hello World" << endl;
return 0;
}
我们发现: C 语言和 C++ 虽然同宗同源,但即使是输出简单的 "Hello World",其背后的设计理念也有很大不同。
① C 语言偏向'面向过程',注重直接的函数调用;
② C++ 偏向'面向对象',引入了流(Stream)和命名空间的概念。
所以,想吃透 C++ 的第一个程序,我们需要先掌握必要的前置知识。
二、命名空间
对于初学 C++ 的朋友,肯定会疑惑这句代码是什么意思,在 C 语言中从来没有看见过:
using namespace std;
其实,命名空间(Namespace)是 C++ 为了解决 C 语言中'命名冲突'这一痛点而引入的关键特性。因为在 C/C++ 中,变量、函数和类都是大量存在的,这些标识符将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
2.1 为什么会出现命名冲突
例如:在头文件 <stdlib.h> 中,包含一个名为 rand 的函数,其原型如下:
rand 函数原型:
int rand(void);
返回值:int
参数:void,不需要传参。
功能:返回一个伪随机整数
#include <stdio.h>
#include <stdlib.h>
int rand = 10; // 定义了一个变量
int main() {
// 编译报错:error C2365: 'rand': 重定义;以前的定义是'函数'
printf("%d\n", rand);
return 0;
}
编译报错原因:
在不知道该函数的前提下,当你编写一个程序时,定义了一个名字也为'rand'的变量,此时命名冲突就发生了。编译器会认为 rand 进行了重定义。因为之前 rand 是一个函数,而现在变成了一个整形变量,这样就导致了因为命名冲突而出现报错。C++ 祖师爷就针对这样的问题,引出了命名空间。
2.2 namespace 的简单定义
定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对 {} 即可,{} 中即为命名空间的成员。命名空间中可以定义变量、函数、类型等。
代码示例:在命名空间 mount 中定义变量、函数、自定义类型。
// namespace -- 关键字 mount:为该命名空间的名字
namespace mount {
// 命名空间中可以定义变量
int rand = 10;
// 可以定义函数
int Add(int left, int right) {
return left + right;
}
// 可以定义结构体变量
struct Node {
struct Node* next;
int val;
};
}
2.3 命名空间解决命名冲突
回顾冲突的原因:同一个域里,不能有两个东西都叫 rand。
在没有命名空间之前,整个代码就像在一个大广场(全局域/Global Scope)上。当你包含了头文件 <stdlib.h> 或 <cstdlib> 时,标准库就把一个叫 rand 的函数放到了这个大广场上。
如果你接着在广场上又定义了一个变量叫 rand:
#include <stdlib.h> // 引入了系统自带的 rand 函数
int rand = 10; // 报错!冲突了!
int main() {
// 编译器困惑:你这里的 rand 到底是指那个函数,还是这个整数?
// Error: 'rand' redeclared as different kind of symbol
}
命名空间解决: 编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。
命名空间(Namespace)的本质就是在广场上盖了一个私有的房间(或者说围了一个院子)。当你定义 namespace mount 时,你创造了一个独立的空间。
#include <stdio.h>
#include <stdlib.h>
// 系统 rand 函数 依然在'广场'上
// 我们建了一个叫 mount 的房间,把我们自己定义的 rand 变量关在里面
namespace mount {
int rand = 10;
}
int main() {
// 此时完全没有冲突:
// 1. 系统 rand 函数,它在外面(全局域)
// 2. 我们的 rand 变量,它在 mount 房间里(mount 域)
return 0;
}
2.4 访问命名空间
编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找,所以下面程序会编译报错。
#include<stdio.h>
namespace mount {
int a = 0;
int b = 1;
}
int main() {
// 编译报错:error C2065: 'a': 未声明的标识符
printf("%d\n", a);
return 0;
}
如果我们想要使用命名空间中定义的变量/函数,有三种方式:
- 指定命名空间访问,大型项目中推荐这种方式。
- using 将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。
- 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。
:: 为域作用限定符,左边无命名空间名时,默认为全局。
- 当域作用限定符左边有命名空间名时,在该命名空间中寻找并调用。
- 当域作用限定符左边没有命名空间名时,默认在全局中寻找并调用。
2.4.1 指定命名空间访问
代码示例:
#include<stdio.h>
namespace mount {
int a = 10;
void f() { printf("func N\n"); }
int add(int a, int b) { return a + b; }
struct Node {
struct Node* next;
int val;
};
}
int c = 10; // 全局变量
int main() {
// 调用命名空间中的变量
printf("%d\n", mount::a);
// 调用命名空间中的无参函数
mount::f();
// 调用命名空间中的有参函数
mount::add(1, 2);
// 声明命名空间中的结构体变量
struct mount::Node n1;
// 调用全局域的变量
printf("%d\n", ::c);
return 0;
}
2.4.2 展开命名空间中某个成员
#include<stdio.h>
namespace mount {
int a = 10;
void f() { printf("func N\n"); }
int add(int a, int b) { return a + b; }
struct Node {
struct Node* next;
int val;
};
}
using mount::a;
using mount::f;
int main() {
// 展开后无需使用域限定符进行访问
printf("%d", a);
f();
return 0;
}
2.4.3 展开命名空间中全部成员
#include<stdio.h>
namespace mount {
int a = 10;
void f() { printf("func N\n"); }
int add(int a, int b) { return a + b; }
struct Node {
struct Node* next;
int val;
};
}
using namespace mount;
int main() {
// 展开命名空间中全体成员后
// 所有成员都无需使用域空间名进行访问
printf("%d", a);
f();
add(1, 2);
struct Node n1;
return 0;
}
这样我们也就理解了为什么出现了 using namespace std;。因为 C++ 标准库都放在一个叫 std(standard) 的命名空间中,我们通过将其展开,无需通过加上前缀 std::,就可以便捷地使用。
2.5 namespace 的嵌套定义
命名空间的嵌套就像电脑文件夹里的'子文件夹'。当项目变得非常巨大时,仅仅一层命名空间可能不够用了。比如:一个大型游戏引擎,可能所有的代码都在 Game 命名空间下,但里面又分'图形'、'音频'、'网络'等模块。
namespace Game {
// Game 下的 Graphics 模块
namespace Graphics {
void render() { printf("正在渲染画面...\n"); }
}
// Game 下的 Audio 模块
namespace Audio {
void playSound() { printf("正在播放声音...\n"); }
}
}
2.6 多文件下的命名空间
多文件中可以定义同名 namespace,它们会默认合并到一起,就像同一个 namespace 一样。
代码实例演示: 假设你在做一个电商系统,所有代码都归属于 Shop 命名空间,但你肯定不会把所有代码写在一个文件里。
文件 1: Cart.h (购物车模块)
// 第一次定义 Shop,编译器创建一个新域
namespace Shop {
void add() { /*...*/ }
}
文件 2: Order.h (订单模块)
// 再次遇到 Shop,编译器识别出已有该域,于是将 Order 加入其中
namespace Shop {
void pay() { /*...*/ }
}
文件 3: main.cpp (主程序)
#include "Cart.h"
#include "Order.h"
int main() {
// 此时,在 main 函数看来,add 函数 和 pay 函数 都在同一个 Shop 命名空间里
Shop::add();
Shop::pay();
return 0;
}
需要注意的'坑':虽然同一个命名空间可以分布在多文件中,但是同一个变量/函数不能重复定义(除非是声明)。
错误示范: 命名空间合并了,意味着 x 都在同一个屋檐下了,在一个屋檐下,不能有两个重名的 x。
FileA.cpp:
namespace A {
int x = 10; // 定义 x
}
FileB.cpp:
namespace A {
int x = 20; // 错误!x 重定义了
}
三、输入与输出
在 C++ 程序中,我们通常包含 <iostream> 头文件,是 Input Output Stream(输入输出流)的缩写。它是 C++ 标准库中的核心头文件 —— 输入输出流库,专门定义了 cout(输出)、cin(输入)等标准输入、输出对象,是实现控制台输入输出的基础。
C++ 的输入输出不再使用函数(如 scanf/printf),而是使用流对象配合运算符。
| 对象/符号 | 所属类 | 作用 | 备注 |
|---|---|---|---|
std::cin | istream | 标准输入(主要面向窄字符 char) | 配合 >> 使用 |
std::cout | ostream | 标准输出(主要面向窄字符 char) | 配合 << 使用 |
std::endl | (函数) | 换行 + 刷新缓冲区 | 比单纯的 \n 多了一个刷新缓冲区的动作 |
>> | - | 流提取运算符 (Input) | 数据从流'流向'变量 |
<< | - | 流插入运算符 (Output) | 数据从变量'流向'屏幕 |
3.1 C++ 中的标准输入
C++ 的输入不再使用函数如 scanf,而是通过定义在 std 命名空间中 std::cin 进行输入。
代码示例:C++ 中的输入需要配合流插入运算符符号 >> 进行使用
#include<iostream>
int main() {
int a;
std::cin >> a;
return 0;
}
代码示例:展开命名空间 std 标准库使用 cin
#include<iostream>
using namespace std;
int main() {
int a;
cin >> a;
return 0;
}
3.2 C++ 中的标准输出
C++ 的输出不再使用函数如 printf,而是通过定义在 std 命名空间中 std::cout 进行输出。
代码示例:C++ 中的输出需要配合流提取符号 << 进行使用
#include<iostream>
int main() {
int a = 10;
std::cout << a;
return 0;
}
代码示例:展开命名空间 std 标准库使用 cout
#include<iostream>
using namespace std;
int main() {
int a = 10;
cout << 10;
return 0;
}
3.3 C++ 中的换行符
std::endl 本质是函数,它的核心区别于单纯的换行符 \n。前者既实现换行,又会刷新缓冲区;而后者仅完成换行,无刷新缓冲区的动作。
代码示例:配合流提取运算符 << 使用换行符
#include<iostream>
using namespace std;
int main() {
int a = 10;
cout << 10 << endl;
return 0;
}
3.4 C++ 的 IO 优势
相比于 C 语言的 printf / scanf,C++ 的方式更加'智能':
- 自动类型识别:不需要像 C 语言那样手动指定格式控制符(如
%d,%c,%f)。原理:本质是利用了函数重载(后续会深入讲解)。 - 支持自定义类型:这是最强大的地方,未来学完类和对象后,你可以让
cin/cout直接输出你自定义的结构体或类。
3.5 C++ 的 IO 注意事项
-
命名空间:
- 所有标准库内容都在
namespace std中。 - 练习时:可以直接
using namespace std;图方便。 - 项目中:不建议直接展开,应使用
std::cout或using std::cout;以免污染命名空间。
- 所有标准库内容都在
-
关于 C 语言混用:
- C++ 代码中依然可以使用
printf/scanf。 - 头文件陷阱:虽然 VS 编译器在包含
<iostream>时可能会间接包含<stdio.h>(让你能直接用 printf),但这不符合标准。 - 为了跨平台兼容,如果用 printf,最好显式包含
<stdio.h>。
- C++ 代码中依然可以使用

