C++ 基础速成篇
C++ 和 C 语言的对比
- C++ 是 C语言的超集:C++ 是 C语言的扩展。几乎所有合法的 C代码在 C++ 中都能正常工作。C++ 在 C语言的基础上增加了面向对象(OOP)特性(类、继承、多态等)、模板、异常处理、STL(标准模板库)等现代编程功能。简单来说,C++包含了 C 语言的所有功能,并在此基础上进行了扩展。
学习 C++ 前是否需要先学习 C语言?
没有学习 C语言也是可以直接学习C++ 的。但是如果有了 C语言基础,可以更好地学习 C++ 相关知识。
- C语言的设计非常接近硬件,它没有过多的抽象,非常适合学习操作系统、嵌入式编程以及深入了解计算机内部工作原理。
- 学习 C语言有助于理解一些重要概念,如内存管理、指针、数据结构等,这些知识对C++ 编程同样适用。
- 如果读者已经熟悉C语言的低级特性(例如指针和内存管理),那么在学习C++时,会更容易理解 C++ 中的复杂特性(如对象的内存管理)。
阅读本文前,最好是拥有C语言的基础,以实现 C++ 基础速成的目的。
学习 C++ 后可以做什么?
- 系统编程:
- 操作系统开发:C++ 由于其接近硬件的特性,广泛用于操作系统开发。可以使用 C++ 开发高效的操作系统或操作系统内核的模块。
- 驱动程序开发:硬件驱动程序通常用 C 或 C++ 编写,它们直接与操作系统和硬件交互。
- 嵌入式开发:C++ 常用于嵌入式系统(如微控制器、物联网设备等),这类系统要求高效和低资源消耗,C++ 的控制性和高性能使其成为理想选择。
- 游戏开发:
- 游戏引擎开发:C++ 是许多知名游戏引擎(如 Unreal Engine)的核心语言。学习 C++ 后,可以参与游戏引擎的开发或在现有引擎上进行定制化开发。
- 游戏开发:C++ 是大多数 AAA 级游戏(如《上古卷轴》系列、《巫师》系列等)的主要开发语言。可以使用 C++ 开发高性能、资源密集型的游戏。
- 图形编程与渲染:C++ 在图形编程中被广泛使用,尤其是在与 OpenGL、DirectX、Vulkan 等图形 API 进行交互时。
- Web 后端开发、人工智能与机器学习等等。
C++ 编译器
- 在 Linux 系统(如 Ubuntu)上,C++ 的代码可以使用
g++
编译器进行编译。(C语言代码使用gcc
编译)。 - 在 Windows 系统上,可以使用 DevCpp 或者 Visual Studio 来编译 C++ 代码。DevCpp 是比较轻量的 C/C++ 编译器,而且是完全免费的,适合用于学习。而 Visual Studio 占内存比较大,虽然功能比较丰富,但是安装时间比较久,学习使用周期长。对于初学者而言,单纯想要快速掌握 C/C++ 语法,使用 DevCpp 编译器即可。如果以后有更大的项目需求,转而学习使用 Visual Studio 即可。(Visual Studio 使用的 C/C++ 编译器是
CL.exe
,链接器是LINK.exe
,资源编译器是RE.exe
)。
不同的 CPU 架构(如 x86
、ARM
等)对应着不同的 CPU 指令集(如 CISC
复杂指令集计算、RISC
精简指令集计算等)。
x86
架构的 CPU 主要用于 桌面计算机、服务器 和一些嵌入式系统(例如工业控制系统、一些高性能计算系统)。ARM
架构的 CPU 主要应用于 移动设备(如智能手机、平板电脑)以及 部分桌面计算机 和 嵌入式系统。
不同的指令集对应着不同的 汇编语言。
- C/C++ 编译器在编译源代码时,首先将源代码转换为 目标指令集 对应的 汇编语言。
- 然后,编译器使用对应平台的 汇编器 将生成的汇编语言代码转换为特定 CPU 架构的 二进制机器码(通常为 目标文件,即
.o
或.obj
文件)。 - 最后,链接器将多个目标文件和库文件连接成一个 可执行程序。
这些可执行程序只能在其对应的 CPU 架构 上运行,因为每种架构的机器码格式和指令集是特定于该架构的。
不同操作系统实现了自己的 C/C++ 编译器工具链,这些工具链用于处理源代码到可执行文件的整个编译过程。
Linux 系统:GCC / G++,GCC/G++ 工具链内置了链接器(通常是 ld
),负责将多个目标文件和库文件链接成最终的可执行文件。
Windows 系统:
- CL.exe:在 Windows 平台上,最常用的 C/C++ 编译器是 Microsoft Visual C++(MSVC)中的 CL.exe。它将 C/C++ 源代码编译成机器代码。
- LINK.exe:这是 Windows 中的链接器,负责将
.obj
目标文件和库文件链接成最终的可执行文件。 - RE.exe:资源编译器,用于将资源文件(如图标、对话框、字符串等)编译成
.res
文件,并将它们与目标文件一起链接成可执行程序。
macOS 系统:
- Clang:在 macOS 上,编译 C/C++ 代码时通常使用 Clang 编译器。Clang 是一个现代化的编译器,兼容 GCC,同时也提供了更好的诊断信息。
- 链接器:macOS 使用 ld 作为链接器,将目标文件和库文件链接成最终的可执行程序。
早期的 C语言编译器本身是由汇编语言开发的,因为在C语言编译器诞生之前不可能使用 C语言来开发C语言的编译器。C 语言最初由 Dennis Ritchie 在 1972 年左右开发,它的第一个编译器(称为 B 编译器)是基于 B 语言 的。B 语言是由 Ken Thompson 在 1969 年开发的,它本身也是由 汇编语言 实现的。因此,最早的 C 编译器并没有使用 C 本身,而是用 汇编语言 来实现。
随着 C 语言的普及和它自身的成熟,C 语言的编译器开始逐步用 C 语言 本身实现。这是由于 C 本身是一种高效且便于移植的语言,适合开发编译器。历史上最具里程碑意义的例子是 第一代 C 编译器,它被用来编译自己。
cc
编译器(通常称为 C 编译器)
- 这个编译器是用 C 语言编写的,标志着 C 语言已经能够用于实现自己的编译器。
- 这个过程被称为 自举(bootstrapping):使用编译器编译自身。
1973 年的 Unix 系统:
- 在 Unix 操作系统的早期版本中,C 编译器开始被用来编译 Unix 系统本身,Unix 操作系统也逐步从汇编语言移植到了 C 语言。
- 这个 C 编译器就是在 Unix 操作系统上开发的。
C++ 语言是在 1983 年由 Bjarne Stroustrup 开发的,它最初是在 C 语言 的基础上扩展而来。最初的 C++ 编译器(例如 Cfront)也是用 C 语言开发的。
- Cfront:
- 第一个 C++ 编译器是 Cfront,由 Stroustrup 开发,最初是用 C 语言 开发的。
- Cfront 将 C++ 源代码翻译为 C 代码,然后再用 C 编译器(通常是
cc
)编译生成目标代码。 - 这个过程称为 源到源编译(source-to-source compilation)。
现代的一些高级编程语言如 Java / Python 的内核都是由 C 或 C++ 来实现的。
JDK 的内核(特别是 JVM 和一些底层功能)是用 C 语言实现的。尽管 Java 本身是一种高级编程语言,其核心部分(如 JVM、垃圾回收器、内存管理、I/O 系统 等)是由 C 或 C++ 编写的,主要原因是 C 语言具有接近硬件的性能,能够高效地处理底层操作。
JVM 是 Java 程序能够跨平台运行的关键。它负责将 Java 字节码 转换为特定平台的机器代码并执行。这也就是为什么相同的 Java 代码可以既可以在 Windows 上运行也可以在 Linux 上运行的原因。
Python 也是同样的道理,Python 代码的运行依赖于 Python 解释器,而Python 解释器在 Windows 上和 Linux 上均有实现。Python 的核心实现(CPython) 是用 C 语言 编写的,负责执行 Python 字节码并管理内存。CPython 是目前最常用的 Python 实现,它使得 Python 代码可以在不同平台(如 Windows、Linux、macOS 等)上运行。
标准输入输出流
C++ 打印 Hello World
:
//learn.cpp
#include <iostream>
int main(void) {
std::cout << "Hello World" << std::endl;
return 0;
}
Linux terminal 下使用 G++ 编译器编译 learn.cpp 源代码的命令:g++ -o hello learn.cpp
,将 learn.cpp 里面的源代码编译成可执行文件 hello,运行使用命令 ./hello
,./
代表当前目录下,hello
代表可执行文件的名字。
jackeysong@jackey-song:~/Desktop/cpp$ ls
learn.cpp
jackeysong@jackey-song:~/Desktop/cpp$ g++ -o hello learn.cpp
jackeysong@jackey-song:~/Desktop/cpp$ ./hello
Hello World
想要使用标准输入输出流,首先要 #include <iostream>
:
<iostream>
是标准 C++ 库的一部分,定义了输入输出流所需的类和对象,例如:
std::cin
:标准输入流。std::cout
:标准输出流。std::cerr
:标准错误流。std::cerr << "Error occurred!" << std::endl;
std::clog
:标准日志流。std::clog << "Log message" << std::endl;
这些对象和相关功能都声明在 <iostream>
中。
主函数的声明和 C语言基本一样:
int main(void) {
return 0;
}
带参数的主函数:
int main(int argc, char* argv[]) {
return 0;
}
接受宽字符参数的形式(C++ 特有):
int main(int argc, wchar_t* argv[]) { //用于处理宽字符命令行参数。
return 0;
}
std::
是 C++ 中 标准命名空间(namespace) 的缩写,用于表示标准库中定义的所有类、函数和对象的作用域。
命名空间的作用
命名冲突的解决:
- 在大型程序中,不同模块可能会定义相同的类或函数名。为了避免名称冲突,C++ 使用命名空间将相关的名字分组。
std
是 C++ 标准库的命名空间,所有标准库中的内容都位于std
命名空间中。- 例如,
cout
、cin
、vector
等标准库对象和类都属于std
。
使用规则:
- 使用标准库内容时,需要在前面加上
std::
以明确作用域,例如std::cout
、std::cin
。
输入/输出运算符 >> 和 <<
输出运算符 <<
:在标准输入输出流中,<<
用于将数据输出到流中(通常是 std::cout
)。
std::cout
:
std::cout
是 C++ 标准库中的 标准输出流对象,用于将数据输出到终端(通常是控制台)。cout
定义在std
命名空间中,所以需要使用std::
前缀。
<<
输出运算符用于将右侧的数据插入到左侧的输出流中。
std::cout << "Hello World" << std::endl;
将字符串 "Hello World"
发送到 std::cout
,从而打印到屏幕。std::endl
是一个操纵符(manipulator),用于向输出流插入一个换行符(等价于 \n
),并刷新输出流缓冲区,确保所有数据都立即输出到终端。在某些情况下,C++ 的输出是缓冲的(即数据可能会暂时存储在内存中,而不是立即输出到屏幕)。std::endl
确保数据立刻被输出,这在实时日志或调试时非常重要。
using
using
是 C++ 中的一个关键字,具有多种用途,主要用于简化命名、创建类型别名以及引入命名空间中的成员。
using
关键字可以用来引入命名空间中的特定成员或整个命名空间,以简化代码书写。
- 引入命令空间中的特定成员:
using namespace_name::member_name;
#include <iostream>
//引入命名空间中的特定成员
using std::cout;
using std::endl;
int main() {
cout << "Hello, World!" << endl; // 无需写 std::
cout << "Jackey Song with C++\n";
cout << "The result of 3 times 4 is :" << 3 * 4 << '\n';
return 0;
}
jackeysong@jackey-song:~/Desktop/cpp$ g++ -o hello learn.cpp
jackeysong@jackey-song:~/Desktop/cpp$ ./hello
Hello, World!
Jackey Song with C++
The result of 3 times 4 is :12
- 引入整个命令空间:
using namespace namespace_name;
- 引入整个命名空间不推荐在大型项目中使用,因为它可能引发命名冲突,特别是当多个命名空间有相同的成员名称时。
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
for (int i = 0; i < argc; i++) {
cout << "第" << i + 1 << "/" << argc << "个参数: " << argv[i] << endl;
}
return 0;
}
jackeysong@jackey-song:~/Desktop/cpp$ g++ -o hello learn.cpp
jackeysong@jackey-song:~/Desktop/cpp$ ./hello Talk is cheap, show me the code.
第1/8个参数: ./hello
第2/8个参数: Talk
第3/8个参数: is
第4/8个参数: cheap,
第5/8个参数: show
第6/8个参数: me
第7/8个参数: the
第8/8个参数: code.
using
可以用来为已有类型创建别名,从 C++11 开始是推荐的写法(取代 typedef
)。
using new_type_name = existing_type_name;
#include <iostream>
#include <vector>
// 创建别名
using IntVector = std::vector<int>;
//typedef std::vector<int> IntVector;
int main() {
IntVector numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
输入运算符 >>
:在标准输入输出流中,>>
用于从流中读取数据(通常是 std::cin
)。
#include <iostream>
using namespace std;
int main() {
int x;
cout << "Enter a number: ";
cin >> x;
cout << "You entered: " << x << endl;
return 0;
}
jackeysong@jackey-song:~/Desktop/cpp$ g++ -o hello learn.cpp
jackeysong@jackey-song:~/Desktop/cpp$ ./hello
Enter a number: 1024
You entered: 1024
根据代码的上下文,<<
/ >>
也可作为位运算符中的左移运算符和右移运算符。
运算符 | 上下文 | 功能 |
---|---|---|
<< | 位运算 | 左移,补 0 ,效果类似乘以 2^n |
>> | 位运算 | 右移,左侧补 0 或符号位,效果类似除以 2^n |
<< | 输出流 | 将右侧数据输出到左侧流(如 std::cout ) |
>> | 输入流 | 从左侧流读取数据到右侧变量(如 std::cin ) |
C++ 中的数据类型
C 和 C++ 共有的基本数据类型
数据类型 | 大小(通常) | 描述 |
---|---|---|
char | 1 字节 | 表示单个字符,通常是 8 位。 |
int | 4 字节 | 表示整数类型。大小可能依赖系统架构。 |
float | 4 字节 | 表示单精度浮点数。 |
double | 8 字节 | 表示双精度浮点数。 |
short | 2 字节 | 短整数,范围小于 int 。 |
long | 4 或 8 字节 | 长整数,范围大于或等于 int 。 |
long long | 通常为 8 字节 | 表示更大的整数。 |
_Bool | 1 字节(C99 引入) | 表示布尔值,true 或 false 。 |
//learn.c
#include <stdio.h>
#include <stdbool.h>
int main() {
_Bool a = true;
if (a) {
printf("Yes\n");
} else {
printf("No\n");
}
return 0;
}
//compile: gcc -o learn learn.cpp
//run: ./learn
Yes
C++ 中特有的数据类型
布尔类型
#include <iostream>
int main() {
bool isTrue = true;
if (isTrue) std::cout << "Yes" << std::endl;
else std::cout << "No" << std::endl;
return 0;
}
Yes
宽字符类型
-
wchar_t
:表示宽字符类型,用于多字节字符。 -
char16_t
和char32_t
(C++11 引入):分别表示 UTF-16 和 UTF-32 编码字符。
char16_t ch16 = u'好';
char32_t ch32 = U'😊';
自动类型推导
-
auto
(C++11 引入):让编译器自动推导变量类型。
-
示例:
auto x = 10; // x 是 int auto y = 3.14; // y 是 double
-
#include <iostream>
using namespace std;
int main() {
auto a = 10; // 推导为 int 类型
auto b = 3.14; // 推导为 double 类型
cout << typeid(a).name() << " " << typeid(b).name() << endl;
return 0;
}
//run:
i d
// i 代表 int
// d 代表 double
字符串类型
C++ 中除了可以使用 C语言中字符数组形式的字符串外,也可以使用 C++ 标准库中的 std::string
类。
#include <iostream>
int main() {
char str[] = "Hello, World"; // 自动添加 '\0' 结尾。C风格的字符串数组
std::cout << str << std::endl;
return 0;
}
#include <iostream>
#include <string> // 引入 string 头文件
using namespace std;
int main() {
std::string str = "Hello, World"; // 使用 std::string 类型初始化
cout << str << endl;
cout << "Length:" << str.length() << endl;
std::cout << "Length:" << str.size() << std::endl;
string str1 = "Jackey ";
string str2 = "Song";
string result = str1 + str2;
cout << "result of str1 + str2: " << result << endl;
return 0;
}
Windows DevCpp 编译运行:
std::string s = "Hello";
std::cout << s[0] << endl; // 使用下标访问第一个字母 'H'
std::cout << s.at(0) << endl; // 使用 at() 方法访问第一个字符 'H',at() 会在访问越界时抛出 std::out_of_range 异常。
// 字符串的拼接除了可以使用 + 号,也可以使用 append() 方法
std::string s1 = "Hello";
std::string s2 = "World";
s1.append(s2);
std::string
字符串比较可以使用 ==、!=、<、> 等操作符。
string s1 = "Hello";
string s2 = "hello";
if (s1 == s2) //判断字符串是否相等,C语言使用 !strcmp(s1, s2)
cout << "s1 equals to s2." << endl;
else
cout << "s1 not equals to s2." << endl;
<
、>
比较字符串的字典顺序(ASCII 码值的比较)。
查找子字符串:find()
,返回子字符串首次出现的位置,没有则返回 std::string::npos
。
#include <iostream>
#include <string>
using namespace std;
void findSubstring(string str, string target) {
size_t pos = str.find(target);
if (pos != string::npos) {
cout << "'" << target << "'" << " found at position " << pos << endl;
} else {
cout << "'" << target << "'" << " not found" << endl;
}
}
int main() {
string s = "Hello everyone. My name is Jackey Song. Nice to meet you.";
findSubstring(s, "Jackey");
findSubstring(s, "Alice");
return 0;
}
'Jackey' found at position 27
'Alice' not found
提取子字符串:substr()
,提取从给定位置开始的指定长度的子字符串。
string s = "Hello everyone. My name is Jackey Song. Nice to meet you.";
cout << s.substr(27, 6) << endl; // 输出 Jackey
替换子字符串:replace()
,用新字符串替换原字符串中的一部分。
string s = "Hello, World!";
s.replace(7, 5, "C++"); // 从位置 7 开始,替换 5 个字符为 "C++"
cout << s << endl; // 输出 "Hello, C++!"
删除部分子字符串:erase()
,用于删除指定位置和长度的字符。
std::string s = "Hello, World!";
s.erase(5, 7); // 从位置 5 开始,删除 7 个字符
std::cout << s << std::endl; // 输出 "Hello!"
插入子字符串:insert()
,在指定位置插入给定的字符串。
std::string s = "Hello!";
s.insert(5, " World"); // 在位置 5 插入 " World"
std::cout << s << std::endl; // 输出 "Hello World!"
大小写转换:std::transform()
,::toupper
/ ::tolower
来实现大小写转换。
#include <algorithm> // 包含了 std::transform()
#include <cctype>
std::string s = "Hello, World!";
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
//字符串开始到结尾进行迭代,输出迭代器为 s.begin() 意味着覆盖原始字符串。::toupper 变为大写。
std::cout << s << std::endl; // 输出 "HELLO, WORLD!"
std::transform()
是 C++ 标准库中的一个算法,通常用来对容器中的元素进行转换。它属于 <algorithm>
头文件,可以用来对字符串、数组或其他容器的元素进行逐个转换,例如将字符串中的字符转换为大写或小写。
::toupper
或 ::tolower
:用于转换每个字符的操作。std::toupper
和 std::tolower
是定义在 <cctype>
中的函数,用于分别将字符转换为大写和小写。
#include <cctype>
是一个 C++ 标准库头文件,提供了一组用于处理字符的 C 风格函数,这些函数主要用于检查和转换单个字符的类型和属性,比如判断字符是否是数字、字母,或者将字符转换为大小写。这些函数是从 C 语言继承过来的,并在 C++ 中可以直接使用。
<cctype>
提供了一系列操作 单个字符 的函数,主要包括:
-
检查字符的属性
这些函数返回一个布尔值,表示字符是否具有某种特定属性。
函数 描述 isalnum(c)
检查字符是否为字母或数字(字母数字字符)。 isalpha(c)
检查字符是否为字母(仅字母字符)。 isdigit(c)
检查字符是否为数字(仅数字字符)。 islower(c)
检查字符是否为小写字母。 isupper(c)
检查字符是否为大写字母。 isspace(c)
检查字符是否为空白字符(如空格、换行、制表符)。 ispunct(c)
检查字符是否为标点符号。 isxdigit(c)
检查字符是否为十六进制数字(0-9, A-F, a-f)。 -
转换字符的大小写
这些函数用于将字符转换为大写或小写字母。
函数 描述 tolower(c)
将字符转换为小写字母。 toupper(c)
将字符转换为大写字母。
字符串转换为数字:std::stoi()
、std::stol()
、std::stod()
等方法将字符串转换为数字。(某些编译器可能不支持这些函数。)
#include <iostream>
#include <string>
int main() {
std::string s_num = "123";
std::string s_pi = "3.1415926";
int num = std::stoi(s_num);
double pi = std::stod(s_pi);
std::cout << num * 10 << std::endl;
std::cout << pi * 100 << std::endl;
return 0;
}
jackeysong@jackey-song:~/Desktop/cpp$ g++ -o hello learn.cpp #编译
jackeysong@jackey-song:~/Desktop/cpp$ ./hello #运行
1230
314.159
string.empty()
检查字符串是否为空。
string a = "";
if (a.empty())
cout << "String a is empty." << endl; //输出: String a is empty.
字符串与 C风格字符串之间的转换:使用 c_str()
方法将 std::string
类型的字符串转换为 C语言风格的字符串 const char*
。
#include <iostream>
#include <string> // C++ 字符串类
#include <string.h> //包含了 C语言 strcmp() 函数的声明。
using namespace std;
int main() {
string s1 = "hello";
string s2 = "hello";
if (s1 == s2)
cout << "C++: s1 equals to s2." << endl;
if (!strcmp(s1.c_str(), s2.c_str()))
printf("C: s1 equals to s2.\n");
return 0;
}
jackeysong@jackey-song:~/Desktop/cpp$ g++ -o hello learn.cpp
jackeysong@jackey-song:~/Desktop/cpp$ ./hello
C++: s1 equals to s2.
C: s1 equals to s2.
面向对象编程思想
面向对象编程(OOP,Object-Oriented Programming)是一种编程范式,它将程序设计为由对象和类组成的系统。面向对象的主要思想是通过封装、继承和多态等机制来组织代码,使程序更易于理解、维护和扩展。
对象 - Object
- 定义:对象是数据和操作数据的函数(称为方法)的集合。对象通常是类的实例。
- 属性:对象的状态由属性(或字段)描述,这些属性通常是类定义的成员变量。
- 行为:对象的行为由类中的方法定义,方法是与对象相关联的操作。
类 - Class
- 定义:类是创建对象的蓝图或模板。类定义了对象的属性(数据)和行为(方法)。通过类,程序员可以创建多个相似类型的对象。
- 构造函数和析构函数:构造函数用于初始化对象的状态,析构函数在对象销毁时执行清理工作。
封装 - Encapsulation
- 定义:封装是将数据(属性)和对数据的操作(方法)打包到一起,并隐藏实现细节,只暴露必要的接口。
- 访问控制:通过
public
、private
和protected
关键字来控制类成员的访问权限。这样,外部代码不能直接访问对象的内部数据,确保数据的安全性和一致性。
继承 - Inheritance
- 定义:继承是通过创建新类来复用、扩展或修改现有类的功能。子类(派生类)继承父类(基类)的属性和方法,可以新增或修改父类的行为。
- 好处:继承提高了代码的复用性,减少了冗余代码。
- 访问控制:通过
public
、protected
和private
继承,控制子类如何继承父类的成员。
多态 - Polymorphism
- 定义:多态是指相同的接口可以表现出不同的行为。多态可以通过方法重载(同名不同参数的方法)和方法覆盖(继承类中重写父类的方法)来实现。
- 虚函数:通过虚函数实现动态绑定,从而支持运行时多态。虚函数在基类中声明,并在派生类中重写。
- 好处:多态性使得代码更加灵活,能够处理不同类型的对象。
抽象 - Abstraction
- 定义:抽象是通过隐藏具体实现来简化复杂系统的过程。抽象化的目的是让程序员关注接口,而不是实现细节。
- 抽象类:抽象类是包含至少一个纯虚函数(没有实现的虚函数)类,不能实例化,但可以作为基类供派生类继承。
C++ 类 class
C++ 中类(class)是面向对象编程的核心,类是创建对象的蓝图,定义了对象的属性(数据成员)和行为(成员函数)。
定义类的语法
class ClassName {
// 访问权限说明符(可选):public, private, protected
// 数据成员(属性)
// 成员函数(方法)
public: // 公有成员:对外可访问
int publicAttribute;
void publicMethod();
private: // 私有成员:仅类内部可访问
int privateAttribute;
void privateMethod();
protected: // 受保护成员:仅类及其子类可访问
int protectedAttribute;
}; //末尾分好不可少
类的成员
- 数据成员
- 数据成员表示类的属性或状态。
- 数据成员可以是基本数据类型、指针、引用、数组或其他类的对象。
- 成员函数
- 成员函数表示类的行为或操作。
- 成员函数通常用于操作或访问数据成员。
- 成员函数可以定义在类内部或类外部。
- 静态成员
- 静态成员变量:所有对象共享,生命周期贯穿程序运行期。
- 静态成员函数:与类关联,可通过类名直接调用。
类的访问控制
C++ 中类的成员有 3 种访问控制方式:
public
(公有):对类外部和类内部都可访问。private
(私有):只能在类内部访问,默认的访问权限。protected
(受保护):只能在类内部和子类中访问。
class Example {
public:
int publicVar; // 公有成员
private:
int privateVar; // 私有成员
protected:
int protectedVar; // 受保护成员
};
类的特性
构造函数 - Constructor
-
定义:
- 构造函数是一个特殊的成员函数,在创建对象时自动调用,用于初始化对象的成员变量。
- 构造函数的名称与类名相同,没有返回值。
-
特点:
- 自动调用:当对象被创建时,构造函数自动执行。
- 支持重载:可以定义多个构造函数(通过不同的参数列表)。
- 默认构造函数:无参构造函数,如果程序员未定义,编译器会自动生成一个默认的无参构造函数。
- 可以使用初始化列表来高效初始化成员变量。
析构函数 - Destructor
- 定义:
- 析构函数是一个特殊的成员函数,在对象生命周期结束时自动调用,用于释放资源、清理对象。
- 析构函数的名称是类名前加一个波浪号(
~
),没有返回值,也不接受参数。
- 特点:
- 自动调用:当对象超出作用域或被显式销毁时(例如通过 delete),析构函数自动执行。
- 每个类只能有一个析构函数(不能重载)。
- 通常用于释放动态分配的资源(如内存、文件句柄等)。
拷贝构造函数 - Copy Constructor
- 定义:
- 拷贝构造函数是用来创建一个对象,并用同类型的另一个对象对它进行初始化。
- 默认拷贝构造函数是由编译器生成的,执行 逐成员拷贝(浅拷贝)。
- 特点:
- 形式为
ClassName(const ClassName& obj)
。 - 当需要 深拷贝 时,程序员需要自定义拷贝构造函数。
- 在以下情况下会调用拷贝构造函数:
- 用一个对象初始化另一个对象,例如
ClassName obj2 = obj1;
- 对象按值传递给函数。
- 函数按值返回对象。
- 用一个对象初始化另一个对象,例如
- 形式为
- 浅拷贝 VS 深拷贝:
- 浅拷贝:只复制成员变量的值,对于指针成员仅复制地址,可能导致悬空指针问题。
- 深拷贝:复制指针指向的内容,为指针成员分配独立的内存。
// 初始化一个对象为另一个对象的副本。
class MyClass {
public:
MyClass(const MyClass& obj) { // 引用 &
cout << "Copy constructor called" << endl;
}
};
常量成员函数
常量成员函数(const member function) 是一种在 C++ 中声明为不能修改对象状态(即不能修改对象的非静态数据成员)的成员函数。
常量成员函数通过在函数声明和定义的尾部加上 const 关键字来标识:
ReturnType FunctionName(ParameterList) const;
- 位置:const 关键字位于参数列表的右侧、函数体的左侧。
- 作用:告诉编译器,该函数不会对所属对象的成员变量进行修改。
静态成员
与类本身相关,可通过类名直接调用。
类的定义与实现分离
头文件与实现文件
- 类的声明通常放在头文件(
.h
文件)。 - 类的实现放在源文件(
.cpp
文件)。
// MyClass.h 头文件
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
private:
int data;
public:
MyClass(int value);
void display();
};
#endif
// 实现文件 MyClass.cpp
#include <iostream>
#include "MyClass.h"
using namespace std;
// 构造函数的实现,使用初始化列表初始化成员变量 data
MyClass::MyClass(int value) : data(value) {}
// display 成员函数的实现
void MyClass::display() {
cout << "Data: " << data << endl;
}
构造函数 MyClass::MyClass(int value)
:
- 使用初始化列表
: data(value)
将value
的值赋给成员变量data
。 MyClass::
指明这是MyClass
类的构造函数。
成员函数 MyClass::display()
:
- 输出
data
的值。 MyClass::
表示该函数属于MyClass
类。
// 主函数
#include "MyClass.h"
int main() {
MyClass obj(10);
obj.display();
return 0;
}
声明与实现分离的优点
-
代码清晰:
- 类的接口和实现分开,头文件只展示接口,源文件包含具体实现。
-
复用性高:
- 头文件可以被多个源文件包含,而实现文件只需编译一次。
-
便于维护:
- 修改实现时,不需要重新编译依赖头文件的其他文件。
C++ 结构体 struct
C语言和C++ 都有结构体,语法也基本相似,但是 C++ 是面向对象的编程语言,在 C语言结构体之上做了扩展,赋予了更多的功能。C++ 的结构体基本上就相当于 类(class)。
访问控制
- 在 C++ 中,结构体的成员默认是 public 权限。
- 在 C 语言中,没有访问控制的概念,所有成员都相当于是 public 。
struct CppStruct {
int x; // 默认是 public
private:
int y; // 必须显示声明私有成员变量
};
struct CStruct {
int x;
int y; //所有成员变量都是 public
};
支持成员函数
#include <iostream>
struct CppStruct {
int x;
void display() { // 成员函数
std::cout << "x = " << x << std::endl;
}
};
int main() {
CppStruct s;
s.x = 10;
s.display();
return 0;
}
C 语言中并不能直接将函数封装到结构体中,但是可以通过函数指针的形式来达到类似的效果:
#include <stdio.h>
struct CStruct {
int x;
void (*display)(struct CStruct*); // 函数指针作为成员变量。
};
void displayFunction(struct CStruct* self) {
printf("x = %d\n", self->x);
}
int main() {
struct CStruct obj;
obj.x = 99;
obj.display = displayFunction; // 将函数指针赋值为具体函数
obj.display(&obj); // 调用 "成员函数"
return 0;
}
struct CStruct {
int x;
void (*setX)(struct CStruct*, int);
void (*display)(struct CStruct*);
};
void setXFunction(struct CStruct* self, int value) {
self->x = value;
}
void displayFunction(struct CStruct* self) {
printf("x = %d\n", self->x);
}
int main() {
struct CStruct obj;
// 初始化函数指针
obj.setX = setXFunction;
obj.display = &displayFunction;
// 调用“成员函数”
obj.setX(&obj, 100);
obj.display(&obj);
return 0;
}
指向函数的指针定义形式:函数的类型标识符 (*指针变量名)(形参类型表);
指向函数的指针赋值:指向函数的指针变量名 = 函数名;
或加上&
取地址运算符: 指向函数的指针变量 = &函数名;
C++ 结构体支持继承和多态
C++ 中的结构体和类没有本质的区别,结构体可以继承其他类或结构体,并支持虚函数和多态。C语言的结构体并没有这些特性。
struct Base { // 定义一个 Base 类
int x;
virtual void show() { // 虚函数
std::cout << "Base x = " << x << std::endl;
}
//虚函数的作用是允许在派生类中重写它,并支持运行时多态(动态绑定)。
};
struct Derived : public Base { // Derived 类 继承 Base 类
void show() override { // 重写虚函数
std::cout << "Derived x = " << x << std::endl;
}
};
Derived
结构体(类)继承方式为 public
,因此 Base
中的 public
成员在 Derived
中仍然是 public
。
重写了基类的虚函数 show()
:
- 使用
override
关键字显式声明,确保这是对基类虚函数的重写。 - 输出信息修改为
Derived x = ...
,以表示该函数来自Derived
类。
虚函数和多态的实现:
-
虚函数 是支持运行时多态的关键。
-
当通过 基类指针或引用 调用虚函数时,会根据对象的实际类型调用对应的版本,而不是编译时决定调用哪一个函数。这种机制称为 动态绑定。
-
如果虚函数没有被标记为
virtual
,调用函数的版本将在编译时确定,表现为 静态绑定。
#include <iostream>
using namespace std;
struct Base {
int x;
virtual void show() {
cout << "Base x = " << x << endl;
}
};
struct Derived : public Base {
void show() override {
cout << "Derived x = " << x << endl;
}
};
int main() {
Base* basePtr; // 定义一个指向 Base 的指针
Derived derivedObj; // 创建 Derived 类的对象
derivedObj.x = 10; // 设置 x 的值
basePtr = &derivedObj; // 基类指针指向派生类对象
basePtr->show(); // 动态绑定,调用 Derived 类的 show()
//运行:Derived x = 10
return 0;
}
basePtr
是一个指向 Base
类型的指针。它指向了一个 Derived
类型的对象(derivedObj
)。当调用 basePtr->show()
时,由于 show()
是虚函数,调用会在运行时动态决定。即使指针的类型是 Base*
,但它指向的是一个 Derived
对象,因此调用的是 Derived
的 show()
函数。
如果没有将 Base::show
声明为 virtual
,调用后输出结果为 Base x = 10
。非虚函数调用是 静态绑定 的,函数的调用在编译时决定。即使 basePtr
指向的是 Derived
对象,调用的仍然是 Base::show
。
在 C++ 中,结构体(struct)和类(class)除了默认访问权限不同外,也没什么太大区别。
struct
的默认访问权限是public
。class
的默认访问权限是private
。
除了默认访问权限,结构体和类在功能上完全相同。都可以定义成员变量和成员函数,支持继承,定义虚函数实现多态。
C++ 结构体支持静态成员
C++ 结构体可以有静态成员变量和静态成员函数。
静态成员变量:
-
静态成员变量是用
static
关键字修饰的变量。 -
它属于 类(或结构体)本身,而不是某个特定的对象。
-
所有对象共享同一个静态成员变量,无论创建多少个对象,静态成员变量只有一份。
// C++ 示例
struct MyStruct {
static int count; // 静态成员变量
static void displayCount() { // 静态成员函数
std::cout << "Count = " << count << std::endl;
}
};
int MyStruct::count = 0; // 初始化静态变量
静态成员变量的特点:
- 全局唯一性:
- 静态成员变量存储在全局(或静态)内存区域,而不是在对象的内存区域中。
- 不随对象的创建或销毁而自动创建或销毁。
- 需要在类外初始化:
- 静态成员变量必须在结构体外部进行显式定义和初始化。
- 定义时不能使用
static
关键字。
- 可以直接通过类名访问:
- 静态成员变量可以通过 类名 或 对象 访问。
- 常用
结构体名::静态变量名
的形式。
- 与普通成员变量的区别:
- 普通成员变量每个对象都有独立的一份,静态成员变量是共享的。
- 静态成员变量不依赖于对象,可以在没有创建对象的情况下访问。
- 作用域和生存期:
- 静态成员变量的作用域是类的作用域。
- 它的生命周期贯穿整个程序的运行期。
#include <iostream>
using namespace std;
struct MyStruct {
static int count; // 静态成员变量声明
int id;
MyStruct(int value) : id(value) {
count++; // 每创建一个对象,计数加一
}
~MyStruct() {
count--; // 每销毁一个对象,计数减一
}
static void showCount() { // 静态成员函数
cout << "Current count: " << count << endl;
}
};
// 静态成员变量定义和初始化
int MyStruct::count = 0;
int main() {
MyStruct::showCount(); // 输出 0,因为还没有创建对象
MyStruct obj1(1);
MyStruct::showCount(); // 输出 1
MyStruct obj2(2);
MyStruct::showCount(); // 输出 2
{
MyStruct obj3(3);
MyStruct::showCount(); // 输出 3
} // obj3 在此处销毁
MyStruct::showCount(); // 输出 2
return 0;
}
静态成员函数:
- 静态成员函数是用
static
关键字修饰的类成员函数。 - 它属于 类本身,而不是某个特定的对象。
- 静态成员函数可以直接访问静态成员变量,但不能直接访问非静态成员变量或成员函数。
静态成员函数的特点:
- 无需对象即可调用:
- 静态成员函数可以通过类名直接调用,无需创建类的对象。
- 形式为
ClassName::StaticFunctionName()
。
- 与对象无关:
- 静态成员函数不能直接访问类的非静态成员(包括成员变量和成员函数),因为它与具体对象无关。
- 它只能访问静态成员变量和其他静态成员函数。
- 没有
this
指针:- 静态成员函数没有隐式的
this
指针,因为它不属于任何对象。
- 静态成员函数没有隐式的
- 常用于全局共享逻辑:
- 静态成员函数通常用于实现一些与类关联的全局逻辑,比如处理静态成员变量的操作或提供某些工具方法。
静态成员函数的特点:
- 不能访问非静态成员:
- 静态成员函数只能直接访问静态成员变量和其他静态成员函数。
- 如果需要访问非静态成员,可以通过传递对象的引用或指针来实现。
- 不能使用
this
指针:- 因为静态成员函数不依赖于任何对象实例,所以它无法使用
this
指针。
- 因为静态成员函数不依赖于任何对象实例,所以它无法使用
- 无法是虚函数:
- 静态成员函数不能声明为虚函数,因为虚函数依赖于对象的动态绑定,而静态成员函数与对象无关。
C++ 结构体支持构造函数、析构函数 和 拷贝构造函数
// C++ 示例
struct MyStruct {
int x;
MyStruct(int val) : x(val) { // 构造函数
std::cout << "MyStruct initialized with x = " << x << std::endl;
}
~MyStruct() { // 析构函数
std::cout << "MyStruct destroyed" << std::endl;
}
};
例子:
Shape.h
头文件:
#ifndef SHAPE_H
#define SHAPE_H
#include <iostream>
#include <cstring> // 用于字符串操作
// 基类:Shape(抽象类)
class Shape {
protected:
char* name; // 动态分配的名称(需要深拷贝处理)
public:
// 构造函数
Shape(const char* shapeName);
// 拷贝构造函数(深拷贝)
Shape(const Shape& other);
// 虚析构函数
virtual ~Shape();
// 纯虚函数:计算面积
virtual double area() const = 0;
// 常量成员函数:获取名称
const char* getName() const;
// 虚函数:打印信息
virtual void print() const;
};
// 派生类:Rectangle(继承自 Shape)
class Rectangle : public Shape {
private:
double width;
double height;
public:
// 构造函数
Rectangle(const char* name, double w, double h);
// 覆写虚函数 area()
double area() const override;
// 覆写虚函数 print()
void print() const override;
};
// 结构体:Point(包含静态成员)
struct Point {
static int count; // 静态成员,记录 Point 实例的数量
double x;
double y;
Point(double x, double y);
~Point(); // 析构函数
};
#endif // SHAPE_H
实现文件 Shape.cpp
:
#include "Shape.h"
#include <iostream>
#include <cstring> // 用于字符串操作
using namespace std;
// 静态成员初始化
int Point::count = 0;
// ----------------- Point 结构体 -----------------
Point::Point(double x, double y) : x(x), y(y) {
count++;
cout << "Point created (" << x << ", " << y << "). Total Points: " << count << endl;
}
Point::~Point() {
count--;
cout << "Point destroyed. Total Points: " << count << endl;
}
// ----------------- Shape 类 -----------------
Shape::Shape(const char* shapeName) {
name = new char[strlen(shapeName) + 1];
strcpy(name, shapeName);
cout << "Shape constructor called for " << name << endl;
}
Shape::Shape(const Shape& other) {
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
cout << "Shape copy constructor called for " << name << endl;
}
Shape::~Shape() {
cout << "Shape destructor called for " << name << endl;
delete[] name;
}
const char* Shape::getName() const {
return name;
}
void Shape::print() const {
cout << "Shape: " << name << endl;
}
// ----------------- Rectangle 类 -----------------
Rectangle::Rectangle(const char* name, double w, double h)
: Shape(name), width(w), height(h) {
cout << "Rectangle constructor called for " << name << endl;
}
double Rectangle::area() const {
return width * height;
}
void Rectangle::print() const {
cout << "Rectangle: " << name << ", Width: " << width << ", Height: " << height
<< ", Area: " << area() << endl;
}
主程序:main.cpp
:
#include "Shape.h"
#include <iostream>
using namespace std;
int main() {
cout << "--- Create Points ---" << endl;
Point p1(1.0, 2.0);
Point p2(3.0, 4.0);
cout << "Total Points: " << Point::count << endl;
cout << "\n--- Create Rectangle ---" << endl;
Rectangle rect("MyRectangle", 5.0, 3.0);
rect.print();
cout << "\n--- Test Shape Copy ---" << endl;
Rectangle rectCopy = rect; // 调用拷贝构造函数
rectCopy.print();
cout << "\n--- Test Polymorphism ---" << endl;
Shape* shape = new Rectangle("PolymorphicRectangle", 7.0, 4.0);
shape->print();
cout << "Area: " << shape->area() << endl;
delete shape; // 确保调用虚析构函数
cout << "\n--- End Program ---" << endl;
return 0;
}
编译:
jackeysong@jackey-song:~/Desktop/cpp/example$ g++ -o main main.cpp Shape.cpp
jackeysong@jackey-song:~/Desktop/cpp/example$ ls
main main.cpp Shape.cpp Shape.h
运行:
ackeysong@jackey-song:~/Desktop/cpp/example$ ./main
--- Create Points ---
Point created (1, 2). Total Points: 1
Point created (3, 4). Total Points: 2
Total Points: 2
--- Create Rectangle ---
Shape constructor called for MyRectangle
Rectangle constructor called for MyRectangle
Rectangle: MyRectangle, Width: 5, Height: 3, Area: 15
--- Test Shape Copy ---
Shape copy constructor called for MyRectangle
Rectangle: MyRectangle, Width: 5, Height: 3, Area: 15
--- Test Polymorphism ---
Shape constructor called for PolymorphicRectangle
Rectangle constructor called for PolymorphicRectangle
Rectangle: PolymorphicRectangle, Width: 7, Height: 4, Area: 28
Area: 28
Shape destructor called for PolymorphicRectangle
--- End Program ---
Shape destructor called for MyRectangle
Shape destructor called for MyRectangle
Point destroyed. Total Points: 1
Point destroyed. Total Points: 0
本篇文章内容已经过长了,对于 C++ 内容的补充会在以后得文章中写。
原文地址:https://blog.csdn.net/m0_46190471/article/details/144199996
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!