C++入门
最简C++程序(HelloWorld)
#include <iostream>
// 引用std名字空间中的所有标识符
using namespace std;
int main()
{
cout << "HelloWorld" << endl;
return 0;
}
头文件
C++头文件与C语言头文件的核心差异与联系:
| 特性 | C语言头文件 | C++头文件 |
|---|---|---|
| 后缀要求 | 必须带.h(如math.h) | 标准头文件无后缀(如iostream) |
| C++对C头文件的兼容 | - | 可兼容.h后缀,优先用c前缀版本(如cmath) |
| 特殊后缀 | 无 | 支持.hpp后缀(自定义头文件常用) |
名字空间
基本概念
名字空间本质是C++中的自定义作用域,设计初衷是解决大规模软件开发中,不同库/模块的全局符号(变量、函数、类等)重名冲突问题。通过为不同模块划分独立作用域,让重名符号可共存,提升编程便利性。
名字空间的定义与使用
定义规则
定义名字空间即定义一个独立作用域,可在其中声明/定义变量、函数、类等符号,语法格式:
// 语法模板
namespace 名字空间名 {
// 变量、函数、类等的定义/声明
数据类型 变量名;
返回值类型 函数名(参数列表);
}
示例代码(分文件编写)
源文件(ns.cpp)- 定义名字空间内的符号
// ns.cpp
// 定义名字空间ns
namespace ns {
// 定义变量
int a = 1;
// 定义函数
float f(int x) {
return x / 2.0f; // 修正:x/2改为x/2.0f,避免整数除法
}
}
// 定义另一个名字空间another_ns
namespace another_ns {
int a = 100; // 与ns中的a重名,但作用域隔离,无冲突
}
头文件(ns.h)- 声明名字空间内的符号
// ns.h
#ifndef NS_H // 补充头文件保护,避免重复包含
#define NS_H
namespace ns {
// 声明符号(extern表示符号定义在其他文件)
extern int a;
extern float f(int x);
}
namespace another_ns {
extern int a;
}
#endif // NS_H
主文件(main.cpp)- 引用名字空间内的符号
// main.cpp
#include <iostream>
#include "ns.h"
using std::cout; // 补充:避免直接导入整个std,减少名字污染
using std::endl;
int main() {
// 使用【作用域引用符::】调用名字空间内的符号
// 语法:名字空间名::符号名
cout << ns::a << endl; // 输出:1
cout << another_ns::a << endl; // 输出:100
cout << ns::f(8) << endl; // 输出:4
return 0;
}
核心语法:作用域引用符(::)
:: 称为作用域引用符,用于访问指定作用域内的符号,常见使用场景:
- 名字空间::符号:访问名字空间内的变量/函数/类
- 类::符号:访问类的静态成员/嵌套类型(后续学习)
- 无前缀::符号:访问全局作用域的符号(例:::a 表示全局变量a)
对比:结构体/类的普通成员用
.访问(如:struct_obj.member),作用域级符号用::访问。
using语句:简化名字空间符号的引用
直接使用 名字空间::符号 会增加代码冗余,using 语句可简化引用,分为两种形式:
两种使用形式
| 形式 | 语法 | 特点 |
|---|---|---|
| 导入整个名字空间 | using namespace 名字空间名; |
便利,但易引发名字污染(未使用的符号成为冲突隐患) |
| 导入指定符号 | using 名字空间名::符号名; |
精准,避免名字污染,推荐优先使用 |
示例对比
形式1:导入整个ns名字空间
// main.cpp
#include <iostream>
#include "ns.h"
using namespace ns; // 导入ns的所有符号
using std::cout;
using std::endl;
int main() {
cout << a << endl; // 等价于ns::a,输出1
cout << f(8) << endl; // 等价于ns::f(8),输出4
return 0;
}
形式2:导入ns的指定符号
// main.cpp
#include <iostream>
#include "ns.h"
using ns::a; // 仅导入ns::a
using ns::f; // 仅导入ns::f
using std::cout;
using std::endl;
int main() {
cout << a << endl; // 输出1
cout << f(8) << endl; // 输出4
return 0;
}
注意事项:避免名字空间污染
- 标准名字空间
std(C++标准库的所有符号都在std中):- 不推荐在头文件中写
using namespace std;(会让所有包含该头文件的代码都导入std,大幅增加冲突风险); - 源文件中若仅用少量std符号(如cout、endl),优先用
using std::cout;而非导入整个std。
- 不推荐在头文件中写
拓展
嵌套名字空间
名字空间可嵌套定义,访问时需逐层指定作用域,适用于大型项目的层级化管理(如按模块/功能划分)。
// 定义嵌套名字空间
namespace project {
namespace module1 {
int num = 10;
void func() {
std::cout << "module1::func" << std::endl;
}
}
namespace module2 {
int num = 20;
}
}
// 访问嵌套名字空间的符号
int main() {
// 方式1:逐层访问
std::cout << project::module1::num << std::endl; // 输出10
project::module1::func(); // 输出module1::func
// 方式2:using简化
using namespace project::module2;
std::cout << num << std::endl; // 输出20
return 0;
}
// C++17简化嵌套定义语法
namespace project::module3 { // 直接嵌套,无需多层{}
int num = 30;
}
匿名名字空间
无名称的名字空间,仅在当前源文件内有效,等价于给符号加static(限制作用域为当前文件),避免全局符号跨文件冲突。
// 匿名名字空间(仅当前.cpp文件可见)
namespace {
int local_num = 5; // 仅当前文件可访问,其他文件无法引用
void local_func() {
std::cout << "仅当前文件可见的函数" << std::endl;
}
}
int main() {
local_num = 10; // 可直接访问,无需作用域符
local_func(); // 正常调用
return 0;
}
内联名字空间(C++11)
用inline修饰的名字空间,其符号可被外层名字空间直接访问,适用于版本兼容场景。
namespace lib {
inline namespace v2 { // 内联名字空间v2
void func() {
std::cout << "lib v2::func" << std::endl;
}
}
namespace v1 { // 非内联
void func() {
std::cout << "lib v1::func" << std::endl;
}
}
}
int main() {
lib::func(); // 直接访问内联名字空间v2的func,输出lib v2::func
lib::v1::func(); // 需显式访问v1的func,输出lib v1::func
return 0;
}
名字空间的别名
为长名字空间定义短别名,简化代码书写,适用于嵌套/长名称的名字空间。
// 定义长名字空间
namespace very_long_namespace_name {
int value = 100;
}
// 定义别名
namespace vlns = very_long_namespace_name;
// 嵌套名字空间的别名
namespace proj_mod1 = project::module1;
int main() {
std::cout << vlns::value << std::endl; // 输出100
std::cout << proj_mod1::num << std::endl; // 输出10
return 0;
}
核心总结
- 名字空间核心作用:隔离全局符号,解决重名冲突,适配大规模软件开发;
- 访问符号的方式:
名字空间::符号(精准)、using语句(简化); - 最佳实践:
- 头文件中避免
using namespace xxx,防止名字污染; - 优先使用
using 名字空间::符号而非导入整个名字空间; - 大型项目用嵌套名字空间做层级管理,用匿名名字空间限制文件内符号作用域;
- 头文件中避免
- 拓展特性:嵌套、匿名、内联名字空间,名字空间别名,可按需提升代码可读性和管理效率。
标准输入、输出
C++的标准流对象替代C语言的printf/scanf,无需关注数据类型,更简洁灵活。所谓的标准流对象,指的是cout、cin和cerr。所谓对象,本质上就是C语言语境中的变量,只不过在面向对象的语境中,这些变量都是自带函数的,它们是名副其实的超级变量,因此为了与C语言的普通变量加以区分,在面向对象的编程语言中,通常将它们称为对象。
核心流对象
| 流对象 | 作用 | 特点 |
|---|---|---|
| cout | 标准输出(控制台) | 经过行缓冲区,效率较高 |
| cin | 标准输入(键盘) | 自动识别数据类型 |
| cerr | 标准错误输出 | 无缓冲区,实时输出 |
| clog | 标准日志输出 | 有缓冲区,适合日志打印 |
基础用法
// 输出
cout << 100 << 3.14 << "abcd" << endl;
// 输入
int a; double f; string s;
cin >> a >> f >> s;
字符串string
C语言无原生字符串类型,依赖字符数组/指针;C++的std::string是系统原生支持的一种基本数据类型,提供丰富操作接口,安全且便捷。
核心基础操作
// 赋值与拷贝
string s1 = "abcd";
string s2 = s1; // 拷贝构造
s2 = s1; // 赋值
// 拼接
string s3 = "xyz" + s1;
s3 += "123"; // 结果:xyzabcd123
// 比对
if (s1 == "abcd") cout << "相等" << endl;
// 元素修改
s1[1] = 'B'; // s1变为"aBcd"
常用接口分类
构造函数
string str1; // 空字符串
string str2("Hello"); // 从C风格字符串构造
string str3(str2); // 拷贝构造
string str4(5, 'A'); // 5个'A' → "AAAAA"
string str5("Hello", 3); // 前3个字符 → "Hel"
string str6(str2, 1, 3); // 索引1开始取3个 → "ell"
容量相关
string str = "Hello";
str.size(); // 长度:5(同length())
str.capacity(); // 分配的存储容量(大于等于size)
str.empty(); // 判断是否为空
str.resize(10); // 调整长度,不足补'\0'
str.reserve(100); // 预留容量,避免频繁扩容
str.shrink_to_fit();// 释放多余容量
元素访问
string str = "Hello";
str[0]; // 访问第0个字符(不检查边界)
str.at(0); // 访问第0个字符(检查边界,越界抛异常)
str.front(); // 第一个字符 → 'H'
str.back(); // 最后一个字符 → 'o'
str.c_str(); // 转为C风格字符串(const char*)
修改操作
// 追加
str.append(" World"); // "Hello World"
str += "!"; // "Hello World!"
str.push_back('?'); // "Hello World!?"
// 插入/删除
str.insert(2, "xxx"); // 在索引2插入 → "Hexxxllo World!?"
str.erase(2, 3); // 从索引2删除3个字符 → 恢复"Hello World!?"
str.clear(); // 清空字符串
// 替换
str.replace(6, 5, "C++");// 索引6开始替换5个字符 → "Hello C++!?"
查找与子串
string str = "Hello World";
size_t pos = str.find("World"); // 查找子串,返回索引6(未找到返回string::npos)
str.rfind("l"); // 从后向前查找'l',返回9
string sub = str.substr(6, 5); // 索引6开始取5个字符 → "World"
数值转换(C++11+)
// 字符串转数值
string str_num = "123.45";
int i = stoi("123"); // 转int
double d = stod(str_num); // 转double
// 数值转字符串
string s_int = to_string(123); // "123"
string s_double = to_string(3.14);// "3.140000"
拓展
- string与C风格字符串转换
// string → const char*
string s = "test";
const char* c_str = s.c_str();
// const char* → string
const char* c = "test";
string s2 = c;
- string的内存管理:string底层是动态字符数组,会自动扩容(通常按2倍扩容),
reserve()可减少扩容次数,提升效率。 - 常见坑点:
[]访问越界不会报错,at()会抛异常;空字符串调用front()/back()会崩溃。
引用
核心概念与语法
引用是“已有变量的别名”,与原变量共享同一块内存,语法:
int a = 100;
int &ra = a; // ra是a的别名,必须定义时赋值
// 操作引用等价于操作原变量
ra = 200; // a的值变为200
核心注意点
- 引用必须定义时赋值,不可单独定义(
int &r;错误); - 引用关系一旦确立,无法修改(不能再指向其他变量);
- 引用不占用额外内存(与指针不同)。
拓展:引用 vs 指针
| 特性 | 引用 | 指针 |
|---|---|---|
| 定义赋值 | 必须定义时赋值 | 可先定义后赋值 |
| 指向修改 | 不可修改 | 可指向其他变量 |
| 内存占用 | 不占用额外内存 | 占用4/8字节(32/64位) |
| 空值 | 无空引用 | 有空指针(NULL/nullptr) |
| 语法复杂度 | 简洁(无解引用符) | 需解引用符(*) |
引用的实用场景
- 函数参数(避免拷贝,提升效率):
void swap(int &x, int &y) {
int temp = x;
x = y;
y = temp;
}
- 函数返回值(避免拷贝,可修改返回值):
int& getVal(int arr[], int idx) {
return arr[idx];
}
// 使用:getVal(arr, 0) = 100; // 直接修改数组第一个元素
类型转换
C++兼容C语言的旧式转换,同时新增更安全、更清晰的新式转换。
旧式转换(不推荐)
char a = 10;
// C语言风格
int b = (int)a;
// C++风格(仅语法调整,无安全性提升)
int c = int(a);
新式转换(推荐)
const_cast:去除const/volatile属性
仅作用于指针/引用,核心用途:修改const修饰的指针/引用指向的变量(注意:修改const变量本身未定义行为)。
// 处理const引用
const int &ri = 6;
const_cast<int &>(ri) = 8; // 可行
// 处理常目标指针
const int *p = &a;
*(const_cast<int *>(p)) = 8; // 可行
// 不可处理常指针本身(char *const p)
int *const p = &a;
const_cast<int *>(p) = &b; // 错误
static_cast:静态类型转换(编译期检查)
- 等价于旧式转换,但可读性更高,且会检查类型兼容性;
- 支持基本类型转换(int→float)、父子类上行转换(子类→父类);
- 不支持不兼容类型转换(如int→float,会编译报错)。
float f = 3.14;
int i = static_cast<int>(f); // 3,安全且易读
int *pi = &i;
float *pf = static_cast<float*>(pi); // 编译报错,不兼容
dynamic_cast:动态类型转换(运行期检查)
专用于类的父子代际转换,仅支持指针/引用,且要求类有虚函数;
- 子类→父类(上行转换):与static_cast等价;
- 父类→子类(下行转换):运行期检查,转换失败返回NULL(指针)/抛异常(引用)。
reinterpret_cast(拓展)
最“强制”的转换,无视类型兼容性,仅重新解释内存布局(极少使用,风险高):
int a = 0x12345678;
char *p = reinterpret_cast<char*>(&a); // 直接将int内存解释为char*
转换使用原则
- 优先用新式转换,避免旧式转换;
- 非必要不使用const_cast(破坏const语义);
- 跨类型转换优先确认兼容性,避免reinterpret_cast。
关键字auto
核心功能
C++11后,auto不再是C语言的“栈变量标记”,而是自动推导变量类型,编译期确定类型。
auto a = 100; // int
auto b = 3.14; // double
auto c = "hello"; // const char*
auto d = string("hi");// string
核心使用场景
- 简化复杂类型(如容器迭代器):
vector<int> vec = {1,2,3};
auto it = vec.begin(); // 替代vector<int>::iterator
- 模板中推导未知类型:
template <typename T>
void show(T &container) {
auto it = container.begin(); // 适配任意容器类型
}
auto的限制
- 不能作为函数参数类型(
void f(auto a)错误,需用模板); - 不能用于类的非静态成员变量;
- 定义时必须初始化(
auto a;错误)。
命名规范(约定俗成)
命名规范无强制标准,但统一的规范可提升代码可读性,C++常用规范:
| 标识符类型 | 命名风格 | 示例 |
|---|---|---|
| 类名 | 大驼峰(帕斯卡) | AppleTree、Student |
| 变量/函数名 | 小驼峰 | appleTree、getScore |
| 常量 | 全大写+下划线 | MAX_NUM、PI |
| 宏 | 全大写+下划线 | #define MIN_VAL 0 |
| 名字空间 | 小写 | namespace my_space |
| 私有成员 | 后缀下划线 | int age_; |
拓展:团队规范优先遵循团队约定,无约定时按上述规范统一。
枚举循环(C++11 range-based for)
核心语法
专用于遍历有确定元素个数的数组、容器,无需关心循环次数:
// 数组遍历
int a[3] = {1,2,3};
for(auto i : a) {
cout << i << endl; // 依次输出1、2、3
}
// 容器遍历(拓展)
vector<string> vec = {"a","b","c"};
for(auto &s : vec) { // 引用避免拷贝
s += "123"; // 修改原容器元素
}
遍历注意点
- 普通遍历(
auto i)是拷贝,修改i不影响原数据; - 需修改原数据用引用(
auto &i); - 只读遍历用const引用(
const auto &i),提升效率。
函数默认参数
核心语法
函数参数可指定默认值,调用时未传参则使用默认值:
// 声明时指定默认参数(推荐)
void f(int a, float b = 3.14);
int main() {
f(100, 1.23); // 使用传入的b=1.23
f(200); // 使用默认的b=3.14
return 0;
}
// 定义时不重复写默认参数
void f(int a, float b) {
cout << a + b << endl;
}
核心规则
- 默认参数必须位于参数列表最右侧(
void f(int a=1, int b)错误); - 声明与定义分离时,仅声明指定默认参数(避免重复定义);
- 默认参数值必须是编译期常量(不能用变量)。
拓展:默认参数的坑点
默认参数在编译期确定,若默认参数依赖外部变量,修改变量不会影响默认值:
int val = 10;
void f(int a = val); // 默认值为10
val = 20;
f(); // 仍使用10,而非20
函数重载
核心概念
同一作用域内,函数名相同、参数列表不同的函数可共存,编译器根据参数区分调用版本(静态多态)。
可形成重载的情形
- 参数个数不同:
void f1(int a);
void f1(int a, int b);
- 参数类型不同:
void f2(int a);
void f2(float b);
- 类方法的const属性不同:
class A {
void f3() const; // const版本
void f3(); // 非const版本
};
- 普通指针与常目标指针不同:
void f4(char *p);
void f4(const char *p);
不可形成重载的情形
- 仅返回值类型不同:
void f1(int a);
float f1(int a); // 冲突,无法重载
- 仅static修饰符不同:
int f2(int a);
static int f2(int a); // 冲突
- 仅参数的const修饰(非指针/引用):
void f3(int a);
void f3(const int a); // 冲突(a是值传递,const无意义)
- 仅常指针本身(非常目标指针):
void f4(char *p);
void f4(char *const p); // 冲突(const修饰指针本身,参数列表无差异)
重载的底层原理(名字改编 Name Mangling)
编译器会根据函数名+参数列表生成唯一的“编译名”,例如:
void f(int)→_Z1fivoid f(float)→_Z1ff
C语言无名字改编,因此不支持重载(可通过extern "C"禁用C++名字改编)。
堆内存管理
C++兼容C的malloc/free,但新增new/delete,更适配面向对象特性。
核心语法对比
| 操作 | C语言 | C++语言 |
|---|---|---|
| 分配单个变量 | int *p = malloc(sizeof(int)); |
int *p = new int; |
| 分配数组 | int *p = calloc(100, sizeof(int)); |
int *p = new int[100]; |
| 释放单个变量 | free(p); |
delete p; |
| 释放数组 | free(p); |
delete [] p;(必须加[]) |
核心差异(类对象场景)
| 操作 | malloc/free | new/delete |
|---|---|---|
| 分配时 | 仅分配内存,不调用构造函数 | 分配内存+自动调用构造函数(初始化) |
| 释放时 | 仅释放内存,不调用析构函数 | 调用析构函数(释放资源)+释放内存 |
示例(类对象):
class Test {
public:
Test() { cout << "构造函数" << endl; }
~Test() { cout << "析构函数" << endl; }
};
int main() {
Test *p1 = (Test*)malloc(sizeof(Test)); // 无构造函数调用
free(p1); // 无析构函数调用
Test *p2 = new Test; // 调用构造函数
delete p2; // 调用析构函数
return 0;
}
拓展知识点
- 内存泄漏:new/delete未成对使用会导致内存泄漏,可使用智能指针(
unique_ptr/shared_ptr)避免; new失败:早期C++返回NULL,C++11后抛bad_alloc异常;- 不能混用
malloc与delete、new与free(行为未定义)。
C++三大核心特性(基础认知)
| 特性 | 核心含义 | 细分类型 |
|---|---|---|
| 封装 | 将数据与操作数据的函数封装为类,隐藏内部实现,仅暴露接口 | - |
| 继承 | 子类复用父类的属性和方法,可拓展或重写父类逻辑 | - |
| 多态 | 同一接口表现不同行为 | 静态多态(编译期):重载 动态多态(运行期):虚函数 |

浙公网安备 33010602011771号