自学内容网 自学内容网

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 架构(如 x86ARM 等)对应着不同的 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 命名空间中。
  • 例如,coutcinvector 等标准库对象和类都属于 std

使用规则

  • 使用标准库内容时,需要在前面加上 std:: 以明确作用域,例如 std::coutstd::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++ 共有的基本数据类型

数据类型大小(通常)描述
char1 字节表示单个字符,通常是 8 位。
int4 字节表示整数类型。大小可能依赖系统架构。
float4 字节表示单精度浮点数。
double8 字节表示双精度浮点数。
short2 字节短整数,范围小于 int
long4 或 8 字节长整数,范围大于或等于 int
long long通常为 8 字节表示更大的整数。
_Bool1 字节(C99 引入)表示布尔值,truefalse
//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_tchar32_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::toupperstd::tolower 是定义在 <cctype> 中的函数,用于分别将字符转换为大写和小写。

#include <cctype> 是一个 C++ 标准库头文件,提供了一组用于处理字符的 C 风格函数,这些函数主要用于检查和转换单个字符的类型和属性,比如判断字符是否是数字、字母,或者将字符转换为大小写。这些函数是从 C 语言继承过来的,并在 C++ 中可以直接使用。

<cctype> 提供了一系列操作 单个字符 的函数,主要包括:

  1. 检查字符的属性

    这些函数返回一个布尔值,表示字符是否具有某种特定属性。

    函数描述
    isalnum(c)检查字符是否为字母或数字(字母数字字符)。
    isalpha(c)检查字符是否为字母(仅字母字符)。
    isdigit(c)检查字符是否为数字(仅数字字符)。
    islower(c)检查字符是否为小写字母。
    isupper(c)检查字符是否为大写字母。
    isspace(c)检查字符是否为空白字符(如空格、换行、制表符)。
    ispunct(c)检查字符是否为标点符号。
    isxdigit(c)检查字符是否为十六进制数字(0-9, A-F, a-f)。
  2. 转换字符的大小写

    这些函数用于将字符转换为大写或小写字母。

    函数描述
    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

  • 定义:封装是将数据(属性)和对数据的操作(方法)打包到一起,并隐藏实现细节,只暴露必要的接口。
  • 访问控制:通过 publicprivateprotected 关键字来控制类成员的访问权限。这样,外部代码不能直接访问对象的内部数据,确保数据的安全性和一致性。

继承 - Inheritance

  • 定义:继承是通过创建新类来复用、扩展或修改现有类的功能。子类(派生类)继承父类(基类)的属性和方法,可以新增或修改父类的行为。
  • 好处:继承提高了代码的复用性,减少了冗余代码。
  • 访问控制:通过 publicprotectedprivate 继承,控制子类如何继承父类的成员。

多态 - 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 种访问控制方式:

  1. public(公有):对类外部和类内部都可访问。
  2. private(私有):只能在类内部访问,默认的访问权限。
  3. 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;
}

声明与实现分离的优点

  1. 代码清晰:

    • 类的接口和实现分开,头文件只展示接口,源文件包含具体实现。
  2. 复用性高:

    • 头文件可以被多个源文件包含,而实现文件只需编译一次。
  3. 便于维护:

    • 修改实现时,不需要重新编译依赖头文件的其他文件。

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 对象,因此调用的是 Derivedshow() 函数。

如果没有将 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; // 初始化静态变量

静态成员变量的特点

  1. 全局唯一性
    • 静态成员变量存储在全局(或静态)内存区域,而不是在对象的内存区域中。
    • 不随对象的创建或销毁而自动创建或销毁。
  2. 需要在类外初始化
    • 静态成员变量必须在结构体外部进行显式定义和初始化。
    • 定义时不能使用 static 关键字。
  3. 可以直接通过类名访问
    • 静态成员变量可以通过 类名对象 访问。
    • 常用 结构体名::静态变量名 的形式。
  4. 与普通成员变量的区别
    • 普通成员变量每个对象都有独立的一份,静态成员变量是共享的。
    • 静态成员变量不依赖于对象,可以在没有创建对象的情况下访问。
  5. 作用域和生存期
    • 静态成员变量的作用域是类的作用域。
    • 它的生命周期贯穿整个程序的运行期。
#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 关键字修饰的类成员函数。
  • 它属于 类本身,而不是某个特定的对象。
  • 静态成员函数可以直接访问静态成员变量,但不能直接访问非静态成员变量或成员函数。

静态成员函数的特点

  1. 无需对象即可调用
    • 静态成员函数可以通过类名直接调用,无需创建类的对象。
    • 形式为 ClassName::StaticFunctionName()
  2. 与对象无关
    • 静态成员函数不能直接访问类的非静态成员(包括成员变量和成员函数),因为它与具体对象无关。
    • 它只能访问静态成员变量和其他静态成员函数。
  3. 没有 this 指针
    • 静态成员函数没有隐式的 this 指针,因为它不属于任何对象。
  4. 常用于全局共享逻辑
    • 静态成员函数通常用于实现一些与类关联的全局逻辑,比如处理静态成员变量的操作或提供某些工具方法。

静态成员函数的特点

  • 不能访问非静态成员:
    • 静态成员函数只能直接访问静态成员变量和其他静态成员函数。
    • 如果需要访问非静态成员,可以通过传递对象的引用或指针来实现。
  • 不能使用 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)!