引言
C++作为一门强大的编程语言,自1983年诞生以来,一直占据着系统级编程和性能敏感领域的核心地位。它不仅继承了C语言的高效性,更引入了面向对象编程等现代特性,成为了工业级软件开发的首选语言之一。本指南将从C++基础开始,逐步深入探讨类和对象的核心概念,帮助你构建完整的C++知识体系。
目录
[1.1 C++简介与发展历史](#1.1 C++简介与发展历史)
[1.2 C++版本演进](#1.2 C++版本演进)
[1.3 C++的重要性与应用领域](#1.3 C++的重要性与应用领域)
[1.4 C++学习建议与书籍推荐](#1.4 C++学习建议与书籍推荐)
[1.5 第一个C++程序](#1.5 第一个C++程序)
[1.6 命名空间(Namespace)](#1.6 命名空间(Namespace))
[1.7 输入输出](#1.7 输入输出)
[1.8 缺省参数(默认参数)](#1.8 缺省参数(默认参数))
[1.9 函数重载(Overload)](#1.9 函数重载(Overload))
[1.10 引用(Reference)](#1.10 引用(Reference))
[1.11 内联函数(Inline)](#1.11 内联函数(Inline))
[1.12 nullptr](#1.12 nullptr)
[2.1 类的定义](#2.1 类的定义)
[2.2 访问限定符](#2.2 访问限定符)
[2.3 类域](#2.3 类域)
[2.4 实例化](#2.4 实例化)
[2.5 对象大小](#2.5 对象大小)
[2.6 this指针](#2.6 this指针)
[2.7 C++与C实现Stack的对比](#2.7 C++与C实现Stack的对比)
[3.1 类的默认成员函数概述](#3.1 类的默认成员函数概述)
[3.2 构造函数](#3.2 构造函数)
[3.3 析构函数](#3.3 析构函数)
[3.4 拷贝构造函数](#3.4 拷贝构造函数)
[3.5 赋值运算符重载](#3.5 赋值运算符重载)
[3.6 日期类实现](#3.6 日期类实现)
[3.7 const成员函数](#3.7 const成员函数)
[3.8 取地址运算符重载](#3.8 取地址运算符重载)
[4.1 再探构造函数(初始化列表)](#4.1 再探构造函数(初始化列表))
[4.2 类型转换](#4.2 类型转换)
[4.3 static成员](#4.3 static成员)
[4.4 友元](#4.4 友元)
[4.5 内部类](#4.5 内部类)
[4.6 匿名对象](#4.6 匿名对象)
[4.7 对象拷贝时的编译器优化](#4.7 对象拷贝时的编译器优化)
第一部分:C++入门基础
1.1 C++简介与发展历史
C++的起源可以追溯到1979年,当时Bjarne Stroustrup在贝尔实验室面对复杂软件开发任务时,感受到了C语言在表达能力、可维护性和可扩展性方面的不足。1983年,他在C语言的基础上添加了面向对象编程的特性,设计出了C++语言的雏形,正式命名为C++。
重要里程碑:
-
1983年:C++诞生,支持类、封装、继承等核心概念
-
1998年:C++98标准发布,第一个官方标准版本
-
2011年:C++11发布,革命性更新,增加大量新特性
-
2020年:C++20发布,引入协程、概念、模块化等现代特性
1.2 C++版本演进
| 版本 | 发布时间 | 主要特性 |
|---|---|---|
| C++98 | 1998年 | 第一个官方标准,引入STL |
| C++03 | 2003年 | 修复错误,引入tr1库 |
| C++11 | 2011年 | lambda、右值引用、智能指针、线程库 |
| C++14 | 2014年 | 泛型lambda、二进制字面量 |
| C++17 | 2017年 | if constexpr、折叠表达式 |
| C++20 | 2020年 | 协程、概念、模块化 |
| C++23 | 2023年 | if consteval、flat_map |
1.3 C++的重要性与应用领域
根据TIOBE 2024年6月的排行榜,C++稳居前三,广泛应用于:
-
游戏开发:Unreal Engine等游戏引擎
-
操作系统:Windows、Linux内核
-
嵌入式系统:智能设备、车载系统
-
机器学习引擎:TensorFlow底层
-
金融系统:高频交易平台
-
音视频处理:FFmpeg、WebRTC
-
服务器端开发:高性能后台服务
1.4 C++学习建议与书籍推荐
学习建议:
-
先动手再理论,从写代码开始
-
循序渐进,每天掌握1-2个概念
-
善用文档和社区资源
推荐书籍:
-
《C++ Primer》:语法大全,适合作为参考书
-
《Effective C++》:教你写出更好的C++代码
-
《STL源码剖析》:深入理解标准库实现
1.5 第一个C++程序
cpp
#include <iostream> // 输入输出库
using namespace std; // 使用标准命名空间
int main() { // 程序入口
cout << "Hello, C++ World!" << endl;
return 0;
}
1.6 命名空间(Namespace)
解决命名冲突的利器,特别是大型项目中。
cpp
namespace CompanyA {
int add(int a, int b) { return a + b; }
}
namespace CompanyB {
int add(int a, int b) { return a + b + 10; }
}
int main() {
cout << CompanyA::add(1, 2) << endl; // 输出3
cout << CompanyB::add(1, 2) << endl; // 输出13
return 0;
}
1.7 输入输出
C++提供了更安全、更方便的输入输出方式:
cpp
#include <iostream>
using namespace std;
int main() {
int a;
double b;
char c;
// 输入
cin >> a >> b >> c;
// 输出
cout << a << " " << b << " " << c << endl;
return 0;
}
1.8 缺省参数(默认参数)
让函数调用更灵活,减少重复代码:
cpp
// 全缺省
void greet(string name = "World", string punctuation = "!") {
cout << "Hello, " << name << punctuation << endl;
}
// 半缺省(必须从右向左连续)
void logMessage(string message, int level = 1, bool timestamp = true) {
// ...
}
1.9 函数重载(Overload)
同一函数名,不同参数列表,让代码更直观:
cpp
void print(int value) {
cout << "整数: " << value << endl;
}
void print(double value) {
cout << "浮点数: " << value << endl;
}
void print(string str) {
cout << "字符串: " << str << endl;
}
1.10 引用(Reference)
引用是变量的别名,共享同一内存空间:
cpp
int main() {
int original = 42;
int& alias = original; // alias是original的别名
alias = 100; // 修改alias就是修改original
cout << original << endl; // 输出100
return 0;
}
1.11 内联函数(Inline)
用空间换时间,适合短小频繁调用的函数:
cpp
inline int square(int x) {
return x * x;
}
int main() {
int result = square(5); // 编译器可能会展开为 5 * 5
cout << result << endl; // 输出25
return 0;
}
1.12 nullptr
更安全、更明确的空指针表示:
cpp
int* ptr = nullptr; // 推荐!明确表示空指针
int* ptr2 = NULL; // 可能被定义为0,可能引起歧义
第二部分:类和对象(上)
2.1 类的定义
类是将数据和操作数据的方法封装在一起的用户自定义数据类型:
cpp
class Date {
public:
// 成员函数
void Init(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 成员变量
int _year;
int _month;
int _day;
};
2.2 访问限定符
C++通过访问限定符实现封装:
-
public:公有成员,类外可以直接访问
-
private:私有成员,只能在类内访问
-
protected:保护成员,类内和派生类中可以访问
cpp
class Stack {
public: // 公有成员,对外提供接口
void Push(int x);
int Top();
private: // 私有成员,外部无法直接访问
int* _array;
size_t _capacity;
size_t _top;
};
2.3 类域
类定义了一个新的作用域,类的所有成员都在类的作用域中:
cpp
class Stack {
public:
void Init(int n = 4); // 声明
private:
int* _array;
};
// 定义需要指定类域
void Stack::Init(int n) {
_array = (int*)malloc(sizeof(int) * n);
// ...
}
2.4 实例化
用类类型创建对象的过程称为实例化:
cpp
int main() {
Date d1; // 实例化对象d1
Date d2; // 实例化对象d2
d1.Init(2024, 3, 31);
d2.Init(2024, 7, 5);
return 0;
}
2.5 对象大小
对象大小由成员变量决定,遵循内存对齐规则:
cpp
class A {
private:
char _ch; // 1字节
int _i; // 4字节,对齐到4的倍数
};
int main() {
A a;
cout << sizeof(a) << endl; // 输出8(1+3填充+4)
return 0;
}
2.6 this指针
编译器为每个成员函数隐式添加this指针,指向调用该函数的对象:
cpp
class Date {
public:
// 编译器转换为:void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day) {
this->_year = year; // 通过this指针访问成员
_month = month; // 等价于this->_month
_day = day;
}
};
2.7 C++与C实现Stack的对比
C语言实现Stack:
c
typedef struct Stack {
int* a;
int top;
int capacity;
} ST;
void STInit(ST* ps);
void STPush(ST* ps, int x);
C++实现Stack:
cpp
class Stack {
public:
void Init(int n = 4);
void Push(int x);
int Top();
private:
int* _a;
size_t _top;
size_t _capacity;
};
主要改进:
-
数据和函数封装在类中
-
通过访问限定符控制访问权限
-
成员函数自动传递this指针
-
支持缺省参数等现代特性
第三部分:类和对象(中)
3.1 类的默认成员函数概述
C++为每个类自动生成6个默认成员函数:
-
构造函数
-
析构函数
-
拷贝构造函数
-
赋值运算符重载
-
取地址运算符重载
-
const取地址运算符重载
3.2 构造函数
构造函数在对象创建时自动调用,用于初始化对象:
cpp
class Date {
public:
// 默认构造函数(无参)
Date() {
_year = 1;
_month = 1;
_day = 1;
}
// 带参构造函数
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
// 全缺省构造函数(也是默认构造函数)
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
};
重要规则:
-
默认构造函数、无参构造函数、全缺省构造函数只能存在一个
-
不写构造函数时,编译器会自动生成默认构造函数
-
编译器生成的默认构造函数对内置类型不处理,对自定义类型调用其默认构造函数
3.3 析构函数
析构函数在对象销毁时自动调用,用于清理资源:
cpp
class Stack {
public:
Stack(int n = 4) {
_a = (int*)malloc(sizeof(int) * n);
_capacity = n;
_top = 0;
}
~Stack() { // 析构函数
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
};
特点:
-
函数名是类名前加~
-
无参数无返回值
-
一个类只能有一个析构函数
-
局部对象后定义先析构
3.4 拷贝构造函数
用于使用已有对象初始化新对象:
cpp
class Date {
public:
// 拷贝构造函数
Date(const Date& d) { // 必须使用const引用
_year = d._year;
_month = d._month;
_day = d._day;
}
};
重要规则:
-
拷贝构造函数是构造函数的重载
-
参数必须是类类型对象的引用
-
编译器默认生成的拷贝构造函数完成浅拷贝
-
有资源管理的类必须自定义拷贝构造函数(深拷贝)
浅拷贝与深拷贝示例:
cpp
// 浅拷贝(默认)
Stack st1;
Stack st2 = st1; // st2._a和st1._a指向同一块内存
// 深拷贝(自定义)
Stack(const Stack& st) {
_a = (int*)malloc(sizeof(int) * st._capacity);
memcpy(_a, st._a, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
3.5 赋值运算符重载
用于两个已存在对象间的赋值:
cpp
class Date {
public:
Date& operator=(const Date& d) {
// 防止自己给自己赋值
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; // 支持连续赋值
}
};
特点:
-
必须重载为成员函数
-
参数建议使用const引用
-
返回值建议使用引用(支持连续赋值)
-
编译器默认生成的赋值运算符完成浅拷贝
3.6 日期类实现
完整的日期类示例:
cpp
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1);
// 关系运算符重载
bool operator<(const Date& d) const;
bool operator==(const Date& d) const;
// 算术运算符重载
Date& operator+=(int day);
Date operator+(int day) const;
// 前置++和后置++
Date& operator++();
Date operator++(int);
// 日期相减
int operator-(const Date& d) const;
private:
int _year;
int _month;
int _day;
int GetMonthDay(int year, int month) const;
};
3.7 const成员函数
const成员函数承诺不修改对象状态:
cpp
class Date {
public:
// const成员函数
void Print() const { // 相当于 const Date* const this
// _year = 2024; // 错误!不能修改成员
cout << _year << "-" << _month << "-" << _day << endl;
}
int GetYear() const { return _year; }
};
重要:
-
const对象只能调用const成员函数
-
非const对象可以调用const成员函数(权限缩小)
-
const成员函数不能调用非const成员函数
3.8 取地址运算符重载
一般不需要自定义,除非特殊需求:
cpp
class Date {
public:
Date* operator&() {
return this; // 正常返回地址
// return nullptr; // 特殊:不让别人取地址
}
const Date* operator&() const {
return this;
}
};
第四部分:类和对象(下)
4.1 再探构造函数(初始化列表)
初始化列表是成员变量初始化的最佳方式:
cpp
class Date {
public:
// 使用初始化列表
Date(int year, int month, int day)
: _year(year) // 直接初始化
, _month(month)
, _day(day)
, _ref(year) // 引用成员必须在初始化列表初始化
, _n(10) // const成员必须在初始化列表初始化
{
// 函数体内是赋值,不是初始化
}
private:
int _year;
int _month;
int _day;
int& _ref; // 引用成员
const int _n; // const成员
};
初始化列表规则:
-
每个成员变量在初始化列表中只能出现一次
-
引用成员、const成员、没有默认构造的自定义类型成员必须在初始化列表初始化
-
初始化顺序按成员声明顺序,与初始化列表中的顺序无关
-
C++11支持在声明处给缺省值
4.2 类型转换
C++支持内置类型到类类型的隐式转换:
cpp
class A {
public:
A(int a) : _a(a) {} // 转换构造函数
// 加explicit禁止隐式转换
// explicit A(int a) : _a(a) {}
};
int main() {
A a = 10; // 隐式转换:10 -> A(10) -> 调用拷贝构造(编译器优化为直接构造)
return 0;
}
4.3 static成员
static成员属于类,不属于任何对象:
cpp
class A {
public:
A() { ++_count; } // 构造时计数
~A() { --_count; } // 析构时计数
static int GetCount() { // 静态成员函数
return _count;
}
private:
static int _count; // 静态成员变量声明
};
// 静态成员变量定义(必须在类外)
int A::_count = 0;
int main() {
A a1, a2;
cout << A::GetCount() << endl; // 输出2
return 0;
}
特点:
-
静态成员变量在类外定义和初始化
-
静态成员函数没有this指针,只能访问静态成员
-
可以通过类名或对象访问静态成员
-
静态成员受访问限定符限制
4.4 友元
友元打破封装,允许外部访问私有成员:
cpp
class Date; // 前置声明
// 友元函数
class Time {
friend void PrintDateTime(const Date& d, const Time& t);
private:
int _hour;
int _minute;
};
class Date {
friend void PrintDateTime(const Date& d, const Time& t);
friend class Time; // 友元类:Time的所有成员函数都是Date的友元
private:
int _year;
int _month;
int _day;
};
void PrintDateTime(const Date& d, const Time& t) {
// 可以访问Date和Time的私有成员
cout << d._year << "-" << d._month << "-" << d._day << " "
<< t._hour << ":" << t._minute << endl;
}
注意:
-
友元关系是单向的
-
友元关系不能传递
-
友元破坏了封装,应谨慎使用
4.5 内部类
类中定义的类称为内部类:
cpp
class Outer {
private:
static int _outer_static;
int _outer_value = 10;
public:
class Inner { // Inner默认是Outer的友元
public:
void VisitOuter(const Outer& outer) {
cout << _outer_static << endl; // 可以访问Outer的静态成员
cout << outer._outer_value << endl; // 可以访问Outer的私有成员
}
};
};
int Outer::_outer_static = 1;
int main() {
Outer::Inner inner; // 通过外部类名访问内部类
Outer outer;
inner.VisitOuter(outer);
return 0;
}
特点:
-
内部类独立于外部类,外部类对象不包含内部类
-
内部类是外部类的友元类
-
内部类受外部类的访问限定符限制
4.6 匿名对象
生命周期只有一行的临时对象:
cpp
class A {
public:
A(int a = 0) : _a(a) {
cout << "A(int a)" << endl;
}
~A() {
cout << "~A()" << endl;
}
int GetValue() { return _a; }
};
int main() {
A(); // 创建匿名对象,本行结束后立即析构
A(10); // 带参数的匿名对象
int value = A(20).GetValue(); // 使用匿名对象调用函数
return 0;
}
4.7 对象拷贝时的编译器优化
现代编译器会优化不必要的拷贝:
cpp
class A {
public:
A(int a = 0) { cout << "A(int a)" << endl; }
A(const A& a) { cout << "A(const A& a)" << endl; }
A& operator=(const A& a) {
cout << "operator=" << endl;
return *this;
}
};
A func() {
A aa;
return aa; // 可能被优化:直接构造返回对象,不生成临时对象
}
int main() {
// 1. 构造+拷贝构造 -> 优化为直接构造
A a1 = 1; // 优化:A a1(1)
// 2. 连续拷贝构造 -> 优化为一个拷贝构造
A a2 = A(2); // 优化:A a2(2)
// 3. 传值返回优化
A a3 = func(); // 可能优化:直接在a3的位置构造
return 0;
}
常见优化场景:
-
传值传参时,构造+拷贝构造 -> 优化为直接构造
-
传值返回时,构造局部对象+拷贝构造临时对象 -> 优化为直接构造返回对象
-
连续构造+拷贝构造 -> 优化为一个构造
关闭优化(g++):
bash
g++ test.cpp -fno-elide-constructors
结语
C++的类和对象是面向对象编程的基石,理解这些概念对于掌握C++至关重要。从基本的类定义、构造函数、析构函数,到复杂的拷贝控制、运算符重载、友元关系,每个部分都有其独特的设计哲学和实用价值。
学习建议:
-
多写代码,实践是理解概念的最佳方式
-
理解每个默认成员函数的行为和适用场景
-
掌握const的正确用法,写出更安全的代码
-
理解编译器优化,但不要依赖特定编译器的优化行为
下一步学习方向:
-
继承与多态:面向对象的核心特性
-
模板与泛型编程:C++的强大特性
-
STL标准库:容器、算法、迭代器
-
智能指针:现代C++内存管理
-
现代C++特性:lambda、移动语义等
记住,学习C++是一场马拉松而非短跑。保持耐心,持续实践,你将逐步掌握这门强大而优雅的语言。祝你编程愉快!