《C++高级编程》读书笔记(一:C++和标准库速成)

news/2024/11/8 5:36:46/

1、参考引用

  • C++高级编程(第4版,C++17标准)马克·葛瑞格尔

2、建议先看《21天学通C++》 这本书入门,笔记链接如下

  • 21天学通C++读书笔记(文章链接汇总)

1. C++ 基础知识

1.1 小程序 “hello world”

// helloworld.cpp
/*helloworld.cpp
*/
#include <iostream>int main() {std::cout << "Hello, World!" << std::endl;return 0;
}
  • 注释

    • 这个程序的前四行分别是单行注释和多行注释,这只是供编程人员阅读的消息,编译器会将其忽略
  • 预处理指令

    • 生成一个 C++ 程序共有三个步骤:首先,代码在预处理器中运行,预处理器会识别代码中的元信息 (meta-information);其次,代码被编译或转换为计算机可识别的目标文件;最后,独立的目标文件被链接在一起变成一个应用程序。
    • #include <iostream> 为预处理指令,预处理指令以 # 字符开始
    • include 指令告诉预处理器:提取 <iostream> 头文件中的所有内容并提供给当前文件
    • 头文件最常见的用途是声明在其他地方定义的函数
    • 函数声明会通知编译器如何调用这个函数,并声明函数中参数的个数和类型,以及函数的返回类型。而函数定义包含了这个函数的实际代码。在 C++ 中,声明通常放在扩展名为 .h 的文件中,称为头文件,其定义通常包含在扩展名为 .cpp 的文件中,称为源文件
    • C 中的标准库头文件在 C++ 中依然存在,但使用以下两个版本
      • 不使用 .h 后缀,改用前缀 c,如 <cstdio>,这些版本放在 std 名称空间中
      • 使用 .h 后缀,这是旧版本,如 <stdio.h>,这些版本不使用名称空间
    • 下面是使用预处理器指令避免重复包含的一个示例
    #ifndef MYHEADER_H
    #define MYHEADER_H
    //...the contents of this header filef
    #endif
    // 或(上下等价)
    #pragma once
    //...the contents of this header filef
    
  • main() 函数

    • main() 函数式程序的入口,可以忽略显式的 return 语句
    • argc 给出了传递给程序的实参数目,argv 包含了这些实参。注意 argv[0] 可能是程序的名称,也可能是空字符串
  • 输入输出流

    // std::cout 用于输出信息
    // std::cerr 用于输出错误信息
    // std::endl 代表序列末尾,换行,表明一行末尾的另一种方法是使用 \n 字符(转义字符)
    // std::cin 接受键盘输入信息
    // 注:printf() 和 scanf() 未提供类型安全,不建议使用
    

1.2 名称空间

  • 名称空间用来处理不同代码段之间的名称冲突问题
    // 头文件 namespaces.h
    namespace mycode {void foo();
    }
    
    // namespaces.cpp
    // 名称空间中还可实现方法或函数
    #include <iostream>
    #include "namespaces.h"void mycode::foo() {std::cout << "foo() called in the mycode namespace" << std::endl;
    }
    
    // usingnamespaces.cpp
    #include "namespaces.h"using namespace mycode;int main() {mycode::foo();	// 调用 mycode 名称空间中的 foo() 函数foo();			// 使用 using 后也可隐式调用return 0;
    }
    

切勿在头文件中使用 using 指令或 using 声明,否则添加你的头文件的每个人都必须使用它

  • C++17 允许方便地使用嵌套的名称空间,即将一个名称空间放在另一个名称空间中
    namespace MyLibraries::Networking::FTP {}
    
  • C++17 还可使用名称空间别名,为另一个名称空间指定一个更简短的新名称
    namespace MyFTP = MyLibraries::Networking::FTP;
    

1.3 字面量

  • 字面量用于在代码中编写数字或字符串
  • 以下字面量指定数字 123
    • 十进制字面量 123
    • 八进制字面量 0173
    • 十六进制字面量 0x7B
    • 二进制字面量 0b1111011
  • 其他字面量
    • 浮点值 (如 3.14f)
    • 双精度浮点值 (如 3.14)
    • 单个字符 (如 ‘a’)
    • 以零结尾的字符数组 (如 "character array”)
    • 还可自定义自变量类型

1.4 变量

  • 在 C++ 中,可在任何位置声明变量,并且可在声明一个变量所在行之后的任意位置使用该变量
  • 当代码中使用了未初始化的变量时,多数编译器会给出警告或报错信息
  • C++ 常见变量类型
    在这里插入图片描述

在这里插入图片描述

  • 类型转换方法
    float myFloat = 3.14f;
    int i1 = (int)myFloat; // 方法一,目前最常使用,但不建议
    int i2 = int(myFloat); // 方法二,很少使用
    int i3 = static_cast<int>(myFloat); // 方法三,最建议使用
    

1.5 运算符

在这里插入图片描述

1.6 类型

  • 枚举类型

    • 枚举类型只是一个整型值,PieceTypeKing 的实际值是 0
    • 如果试图对 PieceType 变量执行算术运算或将其作为整数对待,编译器会给出警告或错误信息
    enum PieceType { PieceTypeKing,PieceTypeQueen,PieceTypeRook,PieceTypePawn
    };
    
  • 还可为枚举成员指定整型值

    • PieceTypeKing 具有整型值 1,编译器为 PieceTypeQueen 赋予整型值 2,PieceTypeRook 的值为 10,编译器自动为 PieceTypePawn 赋予值 11
    enum PieceType { PieceTypeKing = 1,PieceTypeQueen,PieceTypeRook = 10,PieceTypePawn
    };
    
  • 强类型枚举

    • 上面给出的枚举并不是强类型的,这意味着其并非类型安全的。它们总被解释为整型数据,因此可以比较完全不同的枚举类型中的枚举值,强类型的 enum class 枚举解决了这些问题
    enum class PieceType {King = 1,Queen,Rook = 10,Pawn
    }
    
    • 对于 enum class,枚举值名不会自动超出封闭的作用域,这表示总要使用作用域解析操作符
    PieceType piece = PieceType::King;
    
    • 默认情况下,枚举值的基本类型是整型,但可采用以下方式加以改变
    enum class PieceType : unsigned long {King = 1,Queen,Rook = 10,Pawn
    }
    

    建议用类型安全的 enum class 枚举替代类型不安全的 enum 枚举

  • 结构

    • 结构 (struct) 允许将一个或多个已有类型封装到一个新类型中
    // employeestruct.h
    #pragma oncestruct Employee {char firstInitial;char lastInitial;int employeeNumber;int salary;
    }; 
    
    // structtest.cpp
    #include <iostream>
    #include "employeestruct.h"using namespace std;int main() {// Create and populate an employee.Employee anEmployee;anEmployee.firstInitial = 'M';anEmployee.lastInitial = 'G';anEmployee.employeeNumber = 42;anEmployee.salary = 80000;// Output the values of an employee.cout << "Employee: " << anEmployee.firstInitial << anEmployee.lastInitial << endl;cout << "Number: " << anEmployee.employeeNumber << endl;cout << "Salary: $" << anEmployee.salary << endl;return 0;
    }
    

1.7 条件语句

  • if/else 语句

    • if 语句的圆括号中的表达式必须是一个布尔值,或者求值的结果必须是布尔值
    • 0 为 false,非 0 为 true
    if (i > 4) {// Do something.
    } else if {// Do something else.
    } else {// Do something else.
    }
    
  • C++17 允许在 if 语中包括一个初始化器

    if (<initializer> ; <conditional expression>) ( <body> }
    // 示例:初始化器获得一名雇员,以及检查所检索雇员的薪水是否超出 1000 的条件// 只有满足条件才执行 if 语句体
    if (Employee employee = GetEmployee(); employee.salary > 1000) {...
    }
    
  • switch 语句

    • switch 语句的表达式必须是整型、能转换为整型的类型、枚举类型或强类型枚举,必须与一个常量进行比较,每个常量值代表一种 “情况(case)”,如果表达式与这种情况匹配,随后的代码行将会被执行,直至遇到 break 语句为止
    • 此外还可提供 default 情况,如果没有其他情况与表达式值匹配,表达式值将与 default 情况匹配
    switch(menuItem) {case OpenMenuItem:// Code to open a filebreak;case SaveMenuItem:// Code to save a filebreak;default:// Code to give an error messagebreak;
    }
    
    • 与 if 语句一样,C++17 支持在 switch 语句中使用初始化器
    switch (<initializer>; <expression>) {<body>}
    
  • 条件运算符

    • 也称三目运算符,因为它使用三个操作数
    // i 大于 2 吗?如果是真的,结果就是 yes,否则结果就是 no
    std::cout << ((i > 2) ? "yes" : "no");
    

1.8 逻辑比较运算符

  • C++ 对表达式求值时会采用短路逻辑:这意味着一旦最终结果可确定,就不对表达式的剩余部分求值
    在这里插入图片描述

1.9 函数

  • 在 C++ 中,为让其他代码使用某个函数,首先应该声明该函数
    • 如果函数在某个特定的文件内部使用,通常会在源文件中声明并定义这个函数
    • 如果函数是供其他模块或文件使用的,通常会在头文件中声明函数,并在源文件中定义函数
    // 函数声明
    void myFunction(int i, char c);
    // 函数定义
    void myFunction(int i, char c) {std::cout << "the value of i is" << i << std::endl;std::cout << "the value of c is" << c << std::endl;
    }
    // 函数调用
    myFunction(8,a');
    
  • 函数返回类型的推断
    • 要使用这个功能,需要把 auto 指定为返回类型,编译器根据 return 语句使用的表达式推断返回类型
    auto addNumbers(int number1, int number2) {return number1 + number2;
    }
    
  • 当前函数的名称
    • 每个函数都有一个预定义的局部变量 _func_,其中包含当前函数的名称。这个变量的一个用途是用于日志记录
    int addNumbers(int number1, int number2) {std::cout << "Entering function " << _func_ << std::endl;return number1 + number2;
    }
    

1.10 C 风格的数组

  • C++ 中应尽量避免使用 C 风格的数组,而改用 STL 中的 std::array 和 std::vector
    int myArray[3];
    myArray[0];
    myArray[1];
    myArray[2];int myArray[3] = {0};
    int myArray[3] = {};
    int myArray[] = {1, 2, 3, 4};char ticTacToeBoard[3][3];
    ticTacToeBoard[1][1] = 'o';
    

1.11 std::array

  • C++ 有一种大小固定的特殊容器 std::array,这种容器在 <array> 头文件中定义,用 std:array 替代 C 风格的数组会带来很多好处,如下所示
    • 它总是知道自身大小
    • 不会自动转换为指针,从而避免了某些类型的 bug
    • 具有迭代器,可方便地遍历元素
    array<int, 3> arr = {9, 8, 7};
    cout << "Array size = " << arr.size() << endl;
    cout << "2nd element = " << arr[1] << endl;
    

1.12 std::vector

  • 如果希望数组的大小是动态的,推荐使用 std:vector。在 vector 中添加新元素时,vector 会自动增加其大小
    #include <vector> // 包含头文件vector<int> myVector = {11, 22};
    myVector.push_back(33); // 向 vector 中动态添加元素
    myVector.push_back(44);cout << "1st element: " << myVector[0] << endl;
    

1.13 结构化绑定

  • C++17 引入了结构化绑定 (structured bindings) 的概念,允许声明多个变量,这些变量使用数组、结构、pair 或元组中的元素来初始化。例如,假设有下面的数组
    std::array<int, 3> values = {112233};
    
  • 可声明三个变量 x、y 和 z,使用其后数组中的三个值进行初始化。注意,必须为结构化绑定使用 auto 关键字
    auto [x, y, z] = values;
    
  • 使用结构化绑定声明的变量数量必须与右侧表达式中的值数量匹配

1.14 循环

  • while 循环

    • 只要条件表达式的求值结果为 true,while 循环就会重复执行一个代码块
    int i = 0;
    while (i < 5) {std::cout << "This is silly." << std::endl;++i;
    }
    
  • do/while 循环

    • 会首先执行一次代码,而判断是否继续执行的条件检测被放在结尾处。如果想让代码块至少执行一次,并且根据某一条件确定是否多次执行,就可以使用这个循环版本。下面的代码尽管条件为 false,但仍会输出一次
    int i = 100;
    do {std::cout << "This is silly." << std::endl;++i;
    } while (i < 5);
    
  • for 循环

    • for 循环的语法一般更简便,因为可看到循环的初始表达式、结束条件以及每次迭代结束后执行的语句
    for (int i = 0; i < 5; ++i) {std::cout << "This is silly." << std::endl;
    }
    
  • 基于区间的 for 循环

    • 这种循环允许方便地迭代容器中的元素,可用于 C 风格的数组、初始化列表,也可用于具有返回迭代器的 begin() 和 end() 函数的类型:例如 std::array、std::vector
    std::array<int, 4> arr = {1, 2, 3, 4};
    for (int i : arr) {std::cout << i << std::endl;
    }
    

1.15 初始化列表

  • 初始化列表在 <initializer list> 头文件中定义,利用初始化列表,可轻松地编写能接收可变数量参数的函数
  • initializer list 类是一个模板,要求在尖括号之间指定列表中的元素类型,这类似于指定 vector 中存储的对象类型
    #include <iostream>
    #include <initializer_list>using namespace std;int makeSum(initializer_list<int> lst) {int total = 0;for (int value : lst) {total += value;}return total;
    }int main() {int a = makeSum({1, 2, 3});int b = makeSum({10, 20, 30, 40, 50, 60});cout << a << endl;cout << b << endl;return 0;
    }
    

2. 深入研究 C++

2.1 C++ 中的字符串

  • 在 C++ 中使用字符串有三种方法
    • 一种是 C 风格,将字符看成字符数组
    • 一种是 C++ 风格,将字符串封装到一种易于使用的 string 类型中
    • 还有一种是非标准的普通类(第 2 章介绍)
    #include <string>string myString = "Hello, World";
    cout << "The value of myString is " << myString << endl;
    cout << "The second letter is " << myString[1] << endl;
    

2.2 指针和动态内存

  • 动态内存允许所创建的程序具有在编译时大小可变的数据,大多数复杂程序都会以某种方式使用动态内存
2.2.1 堆栈和堆

C++ 程序中的内存分为两个部分:堆栈和堆

  • 堆栈(和栈是一个意思)

    • 堆栈就像一副扑克牌,当前顶部的牌代表程序当前的作用域,通常是当前正在执行的函数。当前函数中声明的所有变量将占用顶部堆栈帧 (也就是最上面的那张牌) 的内存。如果当前函数 foo() 调用了另一个函数 bar(),就会翻开一张新牌,这样 bar() 就会拥有自己的堆栈帧供其运行
    • 任何从 foo() 传递给 bar() 的参数都会从 foo() 堆栈帧复制到 bar 堆栈帧
    • 堆栈帧为每个函数提供了独立的内存空间。如果在 foo() 堆栈中声明了一个变量,那么除非专门要求,否则调用 bar() 函数不会更改该变量
    • foo() 函数执行完毕时,堆栈就会消失,该函数中声明的所有变量都不会再占用内存
    • 在堆栈上分配内存的变量不需要程序员手动释放内存(删除),这个过程是自动完成的
  • 堆栈帧

    • 堆栈帧指的是在堆栈中为当前正在运行的函数分配的区域(或空间)。传入的参数、返回地址(当这个函数结束后必须跳转到该返回地址)以及函数所用的内存单元(即函数存储在堆栈上的局部变量)都在堆栈帧中
    • 堆栈帧通常是在新的函数调用的时候创建,并在函数返回的时候销毁。说白了,堆栈由堆栈帧组成. 当调用函数时堆栈帧被压入栈中, 当函数返回时堆栈帧被从栈中弹出
    • 堆是与当前函数或堆栈帧完全没有关系的内存区域。如果想在函数调用结束之后仍然保存其中声明的变量,可以将变量放到堆中
    • 堆的结构不如堆栈复杂,可以将堆当作一堆位,程序可在任何时候向堆中添加新位或修改堆中已有的位
    • 必须确保释放 (删除) 在堆上分配的任何内存,这个过程不会自动完成,除非使用了智能指针
2.2.2 使用指针
  • 明确地分配内存,就可在堆上放置任何内容。例如,要在堆上放置一个整数,需要给它分配内存,但首先需要声明一个指针
    • 指针只是指向一个整数值的地址。为访问这个值,需要对指针解除引用
    • 可将解除引用看成沿着指针箭头的方向寻找堆中实际的值
    • 解除引用之前指针必须有效。对 null 或未初始化的指针解除引用会导致不可确定的行为
    // 应避免使用未初始化的变量/指针
    int *myIntegerPointer = new int; // 使用 new 操作符分配内存
    int *myIntegerPointer = nullptr; // 如果不希望立即分配内存,可以把它们初始化为空指针
    
  • 给堆中新分配的整数赋值
    *myIntegerPointer = 8; // 没改变指针,只是改变指针指向的内存
    
  • 使用完动态分配的内存后,需要使用 delete 操作符释放内存。为防止在释放指针指向的内存后再使用指针,建议把指针设置为 nullptr
    delete myIntegerPointer;
    myIntegerPointer = nullptr;
    
  • 指针并非总是指向堆内存,可声明一个指向堆栈中变量甚至指向其他指针的指针
    • 为让指针指向某个变量,需要使用 “取址” 运算符 &
    int i = 8;
    int *myIntegerPointer = &i;
    
  • 如果指针指向某个结构,可以首先用 * 对指针解除引用,然后使用普通的 . 语法访问结构中的字段
    Employee *anEmployee = getEmployee();
    cout << (*anEmployee).salary << endl; // 同下等价
    cout << anEmployee->salary << endl; // -> 运算符允许同时对指针解除引用并访问字段
    
2.2.3 动态分配的数组
  • 堆也可以用于动态分配数组。使用 new[] 操作符可给数组分配内存
    • 从下图看出:指针变量仍在堆栈中,但动态创建的数组在堆中
    int arraySize = 8;
    int *myVariableSizedArray = new int[arraySize];
    

在这里插入图片描述

  • 现在已经分配了内存,可将 myVariableSizedArray 当作基于堆栈的普通数组使用
    myVariableSizedArray[3] = 2;
    // 使用完这个数组后,应该将其从堆中删除,这样其他变量就可以使用这块内存
    delete[] myVariableSizedArray;
    myVariableSizedArray = nullptr;
    

避免使用 C 中的 malloc() 和 free(),而使用 new 和 delete,或者使用 new[] 和 delete[]

2.2.4 空指针变量
void func(char* str) {cout << "char* version" << endl;}
void func(int i) {cout <<"int version" << endl;}int main() {func(NULL); // NULL 不是指针,而等价于 0,所以调用整数版本func(nullptr); // 真正的空指针常量 nullptr,调用 char* 版本return 0;
}
2.2.5 智能指针
  • 智能指针对象在超出作用域时,例如在函数执行完毕后,会自动释放内存。C++ 中有两种最重要的智能指针

  • std::unique_ptr

    • 类似于普通指针,但在超出作用域或被删除时,会自动释放内存或资源
    • unique_ptr 只属于它指向的对象
    • 优点:内存和资源始终被释放(即使执行返回语句或抛出异常时)
    • 创建 unique_ptr
    /* unique_ptr 是一个通用的智能指针,它可以指向任意类型的内存所以它是一个模板,而模板需要用尖括号指定模板参数在尖括号中必须指定 unique_ptr 要指向的内存类型make_unique 为 C++14 引入
    */
    auto anEmployee = make_unique<Employee>(); // 不再需要调用 delete,因为会自动完成
    unique_ptr<Employee> anEmployee(new Employee); // C++11 标准
    
    • unique_ptr 也可用于存储 C 风格的数组
    auto employees = make_unique<Employee[]>(10);
    cout << "Salary: " << employees[0].salary << endl;
    
  • std::shared_ptr

    • shared ptr 允许数据的分布式 “所有权”:每次指定 shared ptr 时,都递增一个引用计数,指出数据又多了位 “拥有者”。shared ptr 超出作用域时,就递减引用计数。当引用计数为 0 时,就表示数据不再有任何拥有者,于是释放指针引用的对象
    • 创建 shared_ptr
    auto anEmployee = make_shared<Employee>();
    if (anEmployee) {cout << "Salary: " << anEmployee->salary << endl;
    }
    
    • 从 C++17 开始,也可将数组存储在 shared_ptr 中
    shared_ptr<Employee[]> employees(new Employee[10]);
    cout << "Salary: " << employees[0].salary << endl;
    

2.3 const 的多种用法

2.3.1 使用 const 定义常量
  • 使用 const 取代 #define 定义常量
    const int versionNumberMajor = 2;
    const int versionNumberMinor = 1;
    const std::string productName = "Super Hyper Net Modulator";
    
2.3.2 使用 const 保护参数
void mysteryFunction(const std::string *someString) {*someString ="Test"; // 不允许修改
}int main() {std::string myString = "The string";mysteryFunction(&myString);return 0;
}

2.4 引用

  • 给类型附加一个& ,则指示相应的变量是引用。在幕后它实际上是一个指向原始变量的指针。变量 x 和引用变量 xReference 指向同一个值。如果通过其中一个更改值,则也可在另个中看到更改
    int x = 42;
    int &xReference = x;
    
2.4.1 按引用传递
  • 通常,给函数传递变量时,传递的是值。如果函数接收整型参数,实际上传入的是整数的一个副本,因此不会修改原始变量的值。C 中通常使用栈变量中的指针,以允许函数修改另一个堆栈帧中的变量
  • 在 C++ 中,不是给函数传递指针,而是按引用传递参数
    // 不会影响传递给它的变量,因为变量是按照值传递的
    // 因此函数接收的是传递给它的值的一个副本
    void addOne(int i) {i++;
    }
    // 使用了引用,因此可以改变原始变量的值
    void addOne(int &i) {i++;
    }
    
2.4.2 按 const 引用传递
  • 当向函数传递值时,会制作一个完整副本。当传递引用时,实际上只是传递一个指向原始数据的指针,这样就不需要制作副本。通过传递 const 引用,可做到二者兼顾:不需要副本,原始变量也不会修改
    void printString(const std::string &myString) {std::cout << myString << std::endl;
    }int main() {std::string someString = "Hello world!";printString(someString);printString("Hello World!");return 0;
    }
    

2.5 异常

#include <stdexcept>double divideNumbers(double numerator, double denominator) {if (denominator == 0) {throw invalid_argument("Denominator cannot be 0."); // 函数立刻结束而不会返回值}return numerator / denominator;
}
// 捕获异常并处理
try {std::cout << divideNumbers(2.50.5) << std::endl; // 返回 5std::cout << divideNumbers(2.30) << std::endl; // 抛出异常,不返回值,并直接跳到 catch 块std::cout << divideNumbers(4.52.5) << std::endl; // 程序已跳转,该行不执行
} catch (const invalid_argument &exception) {std::cout << "Exception caught:" << exception.what() << std::endl;
}

2.6 类型推断

2.6.1 关键字 auto
  • auto 可用于告诉编译器,在编译时自动推断变量的类型
  • 但使用 auto 去除了引用和 const 限定符
#include <string>const std::string message = "Test";
const std::string &foo() {return message;
}
// 因为 auto 去除了引用和 const 限定符,且 f1 是 string 类型,所以建立一个副本
auto f1 = foo();
// 如果不需要副本,可使用 auto& 或 const auto&
const auto &f2 = foo();
2.6.2 decltype
  • 关键字 decltype 把表达式作为实参,计算出该表达式的类型
    // 编译器推断出 y 的类型是 int,因为这是 x 的类型
    int x = 123;
    decltype(x) y = 456;
    
  • auto 与 decltype 的区别在于
    • decltype 不会去除引用和 const 限定符
    • 按如下方式使用 decltype 定义f2,导致 f2 的类型为 const string&,从而不生成副本
    decltype(foo()) f2 = foo();
    

3. 作为面向对象语言的 C++

3.1 定义类

  • 在 C++ 中,类通常在头文件(.h)中声明,在对应的源文件(.cpp)中定义其非内联方法和静态数据成员
// AirlineTicket.h
#pragma once#include <string>class AirlineTicket {
public:AirlineTicket(); // 当创建类的对象时会自动调用构造函数~AirlineTicket(); // 当销毁对象时会自动调用析构函数double calculatePriceInDollars() const;const std::string &getPassengerName() const;void setPassengerName(const std::string& name);int getNumberOfMiles() const;void setNumberOfMiles(int miles);bool hasEliteSuperRewardsStatus() const;void setHasEliteSuperRewardsStatus(bool status);private:std::string mPassengerName;int mNumberOfMiles;bool mHasEliteSuperRewardsStatus;
};
  • 这个定义首先声明一个类名,在大括号内声明了类的数据成员(属性)以及方法(行为)
  • 每个数据成员及方法都有特定的访问级别:public、protected 或 private。这些标记可按任意顺序出现,也可重复使用
    • public 成员可在类的外部访问
    • private 成员不能在类的外部访问(推荐把所有的数据成员都声明为 private,在需要时,可通过 public 读取器和设置器来访问它们)
3.1.1 构造函数初始化
  • 方法一:构造函数初始化器
    AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),mNumberOfMiles(0),mHasEliteSuperRewardsStatus(false) {}
    
  • 方法二:将初始化任务放在构造函数体中
    AirlineTicket::AirlineTicket() {mPassengerName = "Unknown Passenger";mNumberOfMiles = 0;mHasEliteSuperRewardsStatus = false;
    }
    
  • 如果构造函数只是初始化数据成员,实际上就没必要使用构造函数,因为可在类定义中直接初始化数据成员。如果类还需要执行其他一些初始化类型,如打开文件、分配内存等,则需要编写构造函数进行处理
    private:std::string mPassengerName = "Unknown Passenger";int mNumberOfMiles = 0;bool mHasEliteSuperRewardsStatus = false;
    
3.1.2 部分 AirlineTicket 类方法的定义
// AirlineTicket.cpp
#include "AirlineTicket.h"
using namespace std;AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),mNumberOfMiles(0),mHasEliteSuperRewardsStatus(false) {}
AirlineTicket::~AirlineTicket() {}double AirlineTicket::calculatePriceInDollars() const {if (hasEliteSuperRewardsStatus()) {// Elite Super Rewards customers fly for free!return 0;}return getNumberOfMiles() * 0.1;
}const string &AirlineTicket::getPassengerName() const {return mPassengerName;
}
...

3.2 使用类

// AirlineTicketTest.cpp
#include <iostream>
#include <memory>
#include "AirlineTicket.h"using namespace std;int main() {// 1. 基于堆栈的类的使用方法AirlineTicket myTicket;myTicket.setPassengerName("Sherman T. Socketwrench");myTicket.setNumberOfMiles(700);double cost = myTicket.calculatePriceInDollars();cout << "This ticket will cost $" << cost << endl;// 2. 基于堆的类的使用方法(使用智能指针)auto myTicket2 = make_unique<AirlineTicket>();myTicket2->setPassengerName("Laudimore M. Hallidue");myTicket2->setNumberOfMiles(2000);myTicket2->setHasEliteSuperRewardsStatus(true);double cost2 = myTicket2->calculatePriceInDollars();cout << "This other ticket will cost $" << cost2 << endl;// No need to delete myTicket2, happens automatically// 3. 基于堆的类的使用方法(不使用智能指针)(不推荐使用该方式)AirlineTicket *myTicket3 = new AirlineTicket();// ... Use ticket 3delete myTicket3;  // delete the heap object!return 0;
} 

4. 统一初始化

struct CircleStruct {int x, y;double radius;
};class CircleClass {
public:CircleClass(int x, int y, double radius): mX(x), mY(y), mRadius(radius) {}
private:int mX, mY;double mRadius;
};
  • C++11 之前,对于结构和类的初始化方式是不同的
    CircleStruct myCirclel = {10102.5};
    CircleClass myCircle2(10102.5);
    
  • C++11 之后,允许一律使用 {…} 语法初始化类型
    // 其中 = 号是可选的
    CircleStruct myCirclel = {10102.5};
    CircleClass myCircle2 = {10102.5};
    
  • 统一初始化还可用来初始化动态分配的数组
int *pArray = new int[4]{0123};
  • 统一初始化还可在构造函数初始化器中初始化类成员数组
class MyClass {
public:MyClass() : mArray{0123} {}
private:int mArray[4];
};
  • 直接列表初始化与复制列表初始化
    • 复制列表初始化:T obj = {arg1, arg2, …};
      • 对于复制列表初始化,放在大括号中的初始化器的所有元素都必须使用相同的类型
    • 直接列表初始化:T obj {arg1, arg2, …};

5. 第一个有用的 C++ 程序

  • 建立一个雇员数据库

5.1 雇员记录系统

  • 管理公司雇员记录的程序应该灵活并具有有效的功能,这个程序包含的功能有

    • 添加雇员
    • 解雇雇员
    • 雇员晋升
    • 查看所有雇员,包括过去以及现在的雇员
    • 查看所有当前雇员
    • 查看所有以前雇员
  • 程序的代码分为三个部分

    • Employee 类封装了单个雇员的信息
    • Database 类管理公司的所有雇员
    • 单独的用户界面提供程序的接口

5.2 Employee 类

  • Employee.h
#pragma once // 防止文件被包含多次
#include <string>// 自定义 Records 名称空间
namespace Records {const int kDefaultStartingSalary = 30000; // 设置新雇员默认起薪class Employee {public:Employee() = default; // 显式的默认构造函数// 包含接收姓名的构造函数Employee(const std::string& firstName, const std::string& lastName);void promote(int raiseAmount = 1000); // 设定了默认值void demote(int demeritAmount = 1000); // 设定了默认值void hire(); // Hires or rehires the employeevoid fire(); // Dismisses the employeevoid display() const;// Outputs employee info to console// 提供修改 set 或查询 get 雇员信息的机制void setFirstName(const std::string& firstName);const std::string& getFirstName() const;void setLastName(const std::string& lastName);const std::string& getLastName() const;void setEmployeeNumber(int employeeNumber);int getEmployeeNumber() const;void setSalary(int newSalary);int getSalary() const;bool isHired() const;private:std::string mFirstName;std::string mLastName;int mEmployeeNumber = -1;int mSalary = kDefaultStartingSalary;bool mHired = false;};
}
  • Employee.cpp
#include <iostream>
#include "Employee.h"using namespace std;namespace Records {Employee::Employee(const std::string &firstName, const std::string &lastName): mFirstName(firstName), mLastName(lastName) {}// 只是用一些新值调用 setSalary() 方法// 注意:整型参数的默认值不显示在源文件中,只能出现在函数声明中,不能出现在函数定义中void Employee::promote(int raiseAmount) {setSalary(getSalary() + raiseAmount);}void Employee::demote(int demeritAmount) {setSalary(getSalary() - demeritAmount);}// 正确设置了 mHired 成员void Employee::hire() {mHired = true;}void Employee::fire() {mHired = false;}// 使用控制台输出流显示当前雇员的信息void Employee::display() const{cout << "Employee: " << getLastName() << ", " << getFirstName() << endl;cout << "-------------------------" << endl;cout << (isHired() ? "Current Employee" : "Former Employee") << endl;cout << "Employee Number: " << getEmployeeNumber() << endl;cout << "Salary: $" << getSalary() << endl;cout << endl;}// 许多获取器(get)和设置器(set)执行获取值以及设置值的任务// 使用这些获取器和设置器的方式要优于将数据成员设置为 public// 1. 方便设置断点,简化调试// 2. 修改类中存储数据的方式时,只需修改这些获取器和设置器void Employee::setFirstName(const string &firstName) {mFirstName = firstName;}const string& Employee::getFirstName() const {return mFirstName;}void Employee::setLastName(const string& lastName) {mLastName = lastName;}const string& Employee::getLastName() const {return mLastName;}void Employee::setEmployeeNumber(int employeeNumber) {mEmployeeNumber = employeeNumber;}int Employee::getEmployeeNumber() const {return mEmployeeNumber;}void Employee::setSalary(int salary) {mSalary = salary;}int Employee::getSalary() const {return mSalary;}bool Employee::isHired() const {return mHired;} 
}

5.3 Database 类

  • Database.h
#pragma#include <iostream>
#include <vector>
#include "Employee.h"namespace Records {const int kFirstEmployeeNumber = 1000;class Database {public:Employee &addEmployee(const std::string &firstName,const std::string &lastName);// 1. 允许按雇员号进行检索Employee &getEmployee(int employeeNumber);// 2. 要求提供雇员姓名Employee &getEmployee(const std::string &firstName,const std::string &lastName);// 输出所有雇员、当前在职雇员和离职雇员的方法void displayAll() const;void displayCurrent() const;void displayFormer() const;private:std::vector<Employee> mEmployees;// 跟踪新雇员的雇员号int mNextEmployeeNumber = kFirstEmployeeNumber;};
}
  • Database.cpp
#include <iostream>
#include <stdexcept>
#include "Database.h"using namespace std;namespace Records {Employee &Database::addEmployee(const string &firstName,const string &lastName) {// 使用输入参数初始化成员变量Employee theEmployee(firstName, lastName);// 数据成员 mNextEmployeeNumber 值递增,因此下一个雇员将获得新编号theEmployee.setEmployeeNumber(mNextEmployeeNumber++);theEmployee.hire(); // 将其聘用状态设置为 "已聘用"mEmployees.push_back(theEmployee);// 返回 mEmployees 向量中的最后一个元素,即新添加的员工return mEmployees[mEmployees.size() - 1];}Employee &Database::getEmployee(int employeeNumber) {// 基于区间的 for 循环遍历 mEmployees 中所有雇员for (auto &employee : mEmployees) {if (employee.getEmployeeNumber() == employeeNumber) {return employee;}}throw logic_error("No employee found.");}Employee &Database::getEmployee(const string &firstName,const string &lastName) {for (auto &employee : mEmployees) {if (employee.getFirstName() == firstName &&employee.getLastName() == lastName) {return employee;}}throw logic_error("No employee found.");}void Database::displayAll() const {for (const auto &employee : mEmployees) {employee.display();}}void Database::displayCurrent() const {for (const auto &employee : mEmployees) {if (employee.isHired()) {employee.display();}}}void Database::displayFormer() const {for (const auto &employee : mEmployees) {if (!employee.isHired()) {employee.display();}}}
}

5.4 用户界面

  • UserInterface.cpp
#include <iostream>
#include <stdexcept>
#include <exception>
#include "Database.h"using namespace std;
using namespace Records;int displayMenu();
void doHire(Database& db);
void doFire(Database& db);
void doPromote(Database& db);int main(int argc, char *argv[]) {Database employeeDB;bool done = false;while (!done) {int selection = displayMenu();switch (selection) {case 0:done = true;break;case 1:doHire(employeeDB);break;case 2:doFire(employeeDB);break;case 3:doPromote(employeeDB);break;case 4:employeeDB.displayAll();break;case 5:employeeDB.displayCurrent();break;case 6:employeeDB.displayFormer();break;default:cerr << "Unknown command." << endl;break;}}return 0;
}int displayMenu() {int selection;cout << endl;cout << "Employee Database" << endl;cout << "-----------------" << endl;cout << "1) Hire a new employee" << endl;cout << "2) Fire an employee" << endl;cout << "3) Promote an employee" << endl;cout << "4) List all employees" << endl;cout << "5) List all current employees" << endl;cout << "6) List all former employees" << endl;cout << "0) Quit" << endl;cout << endl;cout << "---> ";cin >> selection;return selection;
}
// 获取用户输入的新的雇员的姓名,并通知数据库添加这个雇员
void doHire(Database &db) {string firstName;string lastName;cout << "First name?";cin >> firstName;cout << "Last name?";cin >> lastName;db.addEmployee(firstName, lastName);
}
// 要求数据库根据雇员号找到雇员的记录
void doFire(Database &db) {int employeeNumber;cout << "Employee number? ";cin >> employeeNumber;try {Employee &emp = db.getEmployee(employeeNumber);emp.fire();cout << "Employee " << employeeNumber << " terminated." << endl;} catch (const std::logic_error &exception) {cerr << "Unable to terminate employee: " << exception.what() << endl;}
}void doPromote(Database& db) {int employeeNumber;int raiseAmount;cout << "Employee number? ";cin >> employeeNumber;cout << "How much of a raise? ";cin >> raiseAmount;try {Employee& emp = db.getEmployee(employeeNumber);emp.promote(raiseAmount);} catch (const std::logic_error& exception) {cerr << "Unable to promote employee: " << exception.what() << endl;}
}

http://www.ppmy.cn/news/240522.html

相关文章

氮氧发动机设备 手动馅饼机 自动式鞋底切割机 高清碎纸机 1200毫米混合式3D打印机 椰子半自动剥壳机…设计

发动机活塞内燃机车头自动布胶带机自动上盖上料机无人机发动机发动机352柴油机车发动机手动折弯机偏心压机发动机306蜂蜜萃取机电动机车回转式辊道输送机600x600 3D打印机铰接式带式输送机3000运输机水冷工作站计算机松包机阀ID测试机器无人机气动发动机发动机 &#xff08;附动…

AMD黑苹果遇到的问题-机械键盘输入异常

闲来无事搞了个黑苹果&#xff0c;体验效果很好&#xff0c;但是遇到了一些问题&#xff0c;来分享下。 使用usb的机械键盘输入异常&#xff0c;比如打字的时候会有一两个按键反应特别慢。 -解决&#xff1a;在网上找到自己机械键盘开启全键无冲突的快捷键&#xff0c;开启这个…

非苹果专用键盘对应ctrl修改

非苹果键盘&#xff1a;不想改&#xff1a; winc ---对应 ---ctrlc 修改&#xff1a;系统偏好设置--键盘--修饰键--Command 和Contrl 换一下位置[每次换键盘都得设置] Mac内置键盘control、option、command快捷键&#xff0c; 当接入Win键位键盘时&#xff0c;win对应command…

Java 多线程编程

目录 一、简介二、线程的生命周期三、Thread 方法四、创建线程1.通过继承Thread类2.通过实现Runnable接口3.匿名内部类创建线程 五、线程方法应用5.1、sleep()5.2、setDaemon()5.3、interrupt()方法和stop()方法5.4、yield()5.5、join()5.6、线程优先级5.7、synchronized锁5.8、…

utrack调试 艾肯icon_艾肯(iCON)Utrack声卡K歌设置图文教程

由于各种声卡的性能不同以及使用的K歌软件不同&#xff0c;调节的方法也都大不相同&#xff0c;需要根据实际使用的声卡以及k歌软件进行正确的参数调节。以下例举在WINDOWS 7系统下&#xff0c;艾肯Utrack声卡在“YY”或“呱呱K歌伴侣”、“酷我K歌”等K歌软件中使用的调节方法…

艾肯Cube 4Nano MicU声卡等型号安装调试教程

艾肯声卡是用来网络直播或者后期混音编曲的一种声卡类型&#xff0c;是连接在我们电脑USB接口上的&#xff0c;它的型号有很多种&#xff0c;比如&#xff1b;艾肯【icon】Cube 4Nano &#xff0c;MicU&#xff0c;Ultra 4&#xff0c;micu solo &#xff0c;MobileU&#xff0…

艾肯MICU声卡安装调试教程

艾肯【icon】MICU外置声卡是网络娱乐或直播用的一种声卡类型&#xff0c;它是单麦克风接口&#xff0c;而且配制48v供电功能&#xff0c;是比较一款经济实惠的声卡&#xff0c;主要分新版vst和旧版两种。下面我们就来了解一下它的驱动下载&#xff0c;安装&#xff0c;设置以及…

声卡发展史

1984年&#xff0c;英国的Adlib Audio公司迈出了PC多媒体化的第一部&#xff0c;他们推出了第一款魔奇声卡&#xff0c;这款声卡的出现让PC拥有了真正的发声能力&#xff0c;不再是PC喇叭滴滴答答的声音。比起现在的声卡&#xff0c;这块魔奇声卡不但音质上还是功能上&#xff…