作为编程新手,踏入 C++ 的世界,这些基础又关键的知识是绕不开的“必经之路”。从最基础的第一个程序写起,逐步深入命名空间、输入输出、函数特性等内容,帮你搭建起 C++ 入门的知识框架,让你能清晰理解和上手写代码~
一、C++ 的第一个程序
C++ 兼容 C 语言大部分语法,所以用 C 语言实现的 hello world
程序在 C++ 里也能跑,但 C++ 有自己的代码规范,通常把代码后缀改为 .cpp
。不同平台编译器也有差异:
- Windows(VS 编译器):识别
.cpp
文件会调用 C++ 编译器编译。 - Linux:要用
g++
编译,而不是gcc
(gcc
更偏向 C 语言编译,g++
对 C++ 特性支持更完善 )。
代码示例(test.cpp)
// 包含 C 语言标准输入输出头文件,C++ 也能兼容使用
#include <stdio.h>
int main() {
// 输出 hello world,和 C 语言用法一样
printf("hello world\n");
return 0;
}
不过,严格意义上 C++ 版本的 hello world
会用 C++ 专属的输入输出方式,后面会讲到~
二、命名空间(namespace)
在 C++ 编程里,随着代码量变大,很容易出现命名冲突(比如不同模块里的变量、函数重名)。命名空间就是用来解决这个问题的“大杀器”,它能把代码划分到不同的“域”里,让同名内容和平共处。
(一)命名空间的定义
用 namespace
关键字定义命名空间,格式如下:
namespace 命名空间名 {
// 可以定义变量、函数、类型等
int num;
void func() { /* ... */ }
}
本质:就是定义出一个独立的“域”,和全局域相互隔离。比如不同命名空间里可以定义同名变量 rand
,不会冲突。
C++ 里的“域”:C++ 中有函数局部域、全局域、命名空间域、类域。这些“域”会影响编译时语法查找变量/函数/类型的逻辑 :
- 局部域和全局域:除了影响编译查找逻辑,还会影响变量的生命周期(比如局部变量进入作用域创建,离开销毁 )。
- 命名空间域和类域:主要影响编译查找逻辑,不影响变量生命周期。
举个简单例子:
// 定义命名空间 N1
namespace N1 {
int x = 10;
void show() {
// 这里访问的是 N1 命名空间里的 x
printf("%d\n", x);
}
}
namespace N2 {
int x = 20;
void show() {
// 这里访问的是 N2 命名空间里的 x
printf("%d\n", x);
}
}
int main() {
// 调用 N1 的 show 函数,输出 10
N1::show();
// 调用 N2 的 show 函数,输出 20
N2::show();
return 0;
}
(二)命名空间的其他特性
- 定义位置:
namespace
只能定义在全局作用域,不过它支持嵌套定义 。比如:
namespace Outer {
namespace Inner {
int y = 30;
}
}
int main() {
// 访问嵌套命名空间里的变量,输出 30
printf("%d\n", Outer::Inner::y);
return 0;
}
- 多文件同名合并:项目工程中,多个
.cpp
文件里定义的同名namespace
,会被编译器认为是同一个命名空间 。比如file1.cpp
和file2.cpp
都有namespace MySpace
,它们里面的内容会合并到一起。 - 标准库的命名空间:C++ 标准库的内容,都放在
std
(standard 的缩写)这个命名空间里。后面用cout
、cin
等输入输出功能时,就和它密切相关。
(三)命名空间的使用方式
编译器默认只会在局部域或全局域查找变量/函数的声明/定义,不会主动去命名空间里找。所以想用命名空间里的内容,得用下面几种方式:
1. 指定命名空间访问(项目推荐)
格式:命名空间名::成员
,明确告诉编译器去哪个命名空间找。
namespace MySpace {
int data = 100;
void print() {
printf("%d\n", data);
}
}
int main() {
// 明确访问 MySpace 里的 data,输出 100
printf("%d\n", MySpace::data);
// 明确访问 MySpace 里的 print 函数
MySpace::print();
return 0;
}
这种方式最清晰,能避免命名冲突,项目开发里常用。
2. using
展开命名空间某个成员(常用且安全)
如果经常访问命名空间里的某个成员,又不想每次都写 命名空间名::
,可以用 using
展开单个成员:
namespace MySpace {
int data = 100;
void print() {
printf("%d\n", data);
}
}
// 展开 MySpace 里的 data,之后直接用 data 就代表 MySpace::data
using MySpace::data;
int main() {
// 直接用 data,等价于 MySpace::data,输出 100
printf("%d\n", data);
// 但 print 函数还得用命名空间访问,因为没展开它
MySpace::print();
return 0;
}
适合频繁使用某个成员,且成员名不会和其他域里的名字冲突的场景。
3. using namespace
展开全部成员(日常练习可用,项目慎用)
用 using namespace 命名空间名;
可以一次性展开命名空间里所有成员,之后直接用成员名就行。但冲突风险很高,项目里不推荐,容易和其他域的同名内容打架。
namespace MySpace {
int data = 100;
void print() {
printf("%d\n", data);
}
}
// 展开 MySpace 所有成员
using namespace MySpace;
int main() {
// 直接用 data 和 print,等价于 MySpace::data、MySpace::print
printf("%d\n", data);
print();
return 0;
}
日常写小练习代码图方便可以用,但正式项目为了代码安全和可读性,尽量别这么干~
三、C++ 输入 & 输出
C++ 有自己专属的输入输出方式,比 C 语言的 printf
、scanf
更方便,不需要手动指定格式,还能更好支持自定义类型。
(一)核心头文件和对象
<iostream>
:是 Input Output Stream 的缩写,包含了标准输入、输出流的相关定义,要使用 C++ 输入输出,必须包含这个头文件。std::cin
:是istream
类的对象,用于标准输入(从键盘等设备读数据 ),面向窄字符(char
类型 )。std::cout
:是ostream
类的对象,用于标准输出(往控制台打印数据 ),面向窄字符。std::endl
:是一个特殊的“操作符”,作用是插入换行符并刷新缓冲区,让输出立马显示在控制台。<<
和>>
:<<
是流插入运算符,配合cout
用,把数据输出到控制台。>>
是流提取运算符,配合cin
用,从输入设备读数据。在 C 语言里,这两个符号是位运算的左移、右移,C++ 里属于运算符重载(后面会深入讲,新手先记住用法 )。
(二)基础用法示例
#include <iostream>
// 展开 std 命名空间(小练习常用,项目看情况)
using namespace std;
int main() {
int num;
// 输出提示信息,endl 换行
cout << "请输入一个数字:" << endl;
// 从键盘读入一个整数,存到 num
cin >> num;
// 输出 num 的值,自动识别类型
cout << "你输入的数字是:" << num << endl;
return 0;
}
运行后,程序会等待你从键盘输入数字,然后输出你输入的内容。和 printf
、scanf
相比,不用关心格式符(%d
、%f
这些 ),C++ 会自动识别变量类型,是不是方便很多~
(三)提升 IO 效率的小技巧(竞赛场景常用)
在一些大量输入输出的场景(比如算法竞赛 ),默认的 IO 速度可能不够快。可以加这几行代码提升效率:
#include <iostream>
using namespace std;
int main() {
// 关闭 stdio 和 iostream 的同步,提升效率
ios_base::sync_with_stdio(false);
// 解除 cin 和 cout 的绑定(默认绑定在一起,会有额外开销)
cin.tie(nullptr);
cout.tie(nullptr);
// 之后正常用 cin、cout 就行,速度会更快
int a, b;
cin >> a >> b;
cout << a + b << endl;
return 0;
}
原理是减少 IO 底层的一些“同步开销”,让数据读写更高效,竞赛里处理大数据输入输出时很有用~
四、缺省参数(默认参数)
缺省参数是 C++ 函数的一个实用特性,让函数调用更灵活。简单说,就是给函数参数预先设置一个“默认值”,调用函数时,没传对应参数就用默认值,传了就用传入的值 。
(一)分类和规则
- 全缺省参数:函数所有参数都有默认值。
// 全缺省,a 默认 10,b 默认 20,c 默认 30
int add(int a = 10, int b = 20, int c = 30) {
return a + b + c;
}
int main() {
// 传 0 个参数,用全部默认值:10+20+30=60
cout << add() << endl;
// 传 1 个参数,a=1,b=20,c=30 → 51
cout << add(1) << endl;
// 传 2 个参数,a=1,b=2,c=30 → 33
cout << add(1, 2) << endl;
// 传 3 个参数,a=1,b=2,c=3 → 6
cout << add(1, 2, 3) << endl;
return 0;
}
- 半缺省参数:只有部分参数有默认值,且必须从右往左连续缺省,不能跳着给默认值。
// 半缺省,从右往左,b 有默认值,a 没有(错误示例,不符合连续缺省规则)
// int add(int a, int b = 20, int c);
// 正确示例:c 有默认值,b 没有的话不行;应该保证从右往左连续
int add(int a, int b, int c = 30) {
return a + b + c;
}
int main() {
// 传 2 个参数,a=1,b=2,c=30 → 33
cout << add(1, 2) << endl;
// 传 3 个参数,a=1,b=2,c=3 → 6
cout << add(1, 2, 3) << endl;
return 0;
}
调用规则:传参时必须从左到右依次给实参,不能跳着传。比如函数 void func(int a = 1, int b = 2, int c = 3);
,不能只传 a
和 c
,必须按顺序传。
(二)声明和定义分离时的注意点
如果函数的声明和定义分开写(比如头文件声明,cpp 文件定义 ),缺省参数只能在声明里给,定义里不能重复给 。否则编译器会报错,因为默认参数只能有一份“定义”。
示例(正确写法 ):
test.h
#ifndef TEST_H
#define TEST_H
// 声明时给缺省参数
int add(int a = 10, int b = 20, int c = 30);
#endif
test.cpp
#include "test.h"
// 定义时不能再给缺省参数,否则冲突
int add(int a, int b, int c) {
return a + b + c;
}
main.cpp
#include "test.h"
#include <iostream>
using namespace std;
int main() {
cout << add() << endl; // 用默认值,输出 60
return 0;
}
五、函数重载
C++ 支持同一作用域下定义同名函数,但这些函数的形参必须不同(参数个数、类型、顺序不同 )。这就是函数重载,能让函数调用更灵活,体现“多态”特性(C 语言不支持同一作用域同名函数,会报错 )。
(一)示例说明
#include <iostream>
using namespace std;
// 1. 参数个数不同
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
// 2. 参数类型不同
double add(double a, double b) {
return a + b;
}
// 3. 参数顺序不同(虽然少见,但符合规则)
int add(int a, double b) {
return a + static_cast<int>(b);
}
int add(double a, int b) {
return static_cast<int>(a) + b;
}
int main() {
// 调用 add(int, int) → 3
cout << add(1, 2) << endl;
// 调用 add(int, int, int) → 6
cout << add(1, 2, 3) << endl;
// 调用 add(double, double) → 3.3
cout << add(1.1, 2.2) << endl;
// 调用 add(int, double) → 1 + 2 = 3
cout << add(1, 2.0) << endl;
// 调用 add(double, int) → 1 + 2 = 3
cout << add(1.0, 2) << endl;
return 0;
}
编译器会根据你传入的参数类型、个数、顺序,自动匹配对应的函数,是不是很智能~
(二)注意事项
- 作用域限制:函数重载必须在同一作用域(比如全局作用域、同一个命名空间、同一个类 )。不同作用域的同名函数不算重载,会引发命名冲突。
- 返回值不参与重载判断:不能仅靠返回值不同来实现重载。比如
int func();
和double func();
,编译器无法区分调用哪个,会报错。
六、引用
引用是 C++ 里很重要的特性,常用来替代指针,让代码更简洁、可读性更高。
(一)引用的概念和定义
引用不是新定义变量,而是给已存在的变量取一个“别名” 。编译器不会为引用开辟新内存空间,它和原变量共用同一块内存 。
形象点说:就像《水浒传》里,李逵有“铁牛”“黑旋风”这些别名;林冲外号“豹子头”。变量的引用,就相当于给它起了个新外号,操作引用和操作原变量,效果一样。
语法格式:类型& 引用名 = 原变量;
示例:
#include <iostream>
using namespace std;
int main() {
int num = 10;
// ref 是 num 的引用(别名)
int& ref = num;
// 操作 ref,等价于操作 num → num 变成 20
ref = 20;
// 输出 num 的值,结果是 20
cout << num << endl;
return 0;
}
这里 ref
和 num
指向同