"C++ Advanced Programming" reading notes (1: C++ and standard library crash course)

1. References

2. It is recommended to read the book "21 Days to Learn C++" to get started. The link of the notes is as follows

1. Basic knowledge of C++

1.1 Applet "hello world"

// helloworld.cpp
/*
    helloworld.cpp
*/
#include <iostream>

int main() {
    
    
    std::cout << "Hello, World!" << std::endl;
    
    return 0;
}
  • note

    • The first four lines of this program are single-line comments and multi-line comments, which are just messages for programmers to read and will be ignored by the compiler
  • preprocessing directive

    • There are three steps in generating a C++ program: first, the code runs in the preprocessor, which recognizes the meta-information in the code; second, the code is compiled or converted into an object file recognizable by the computer; Finally, the individual object files are linked together into an application.
    • #include <iostream> is a preprocessing directive, and the preprocessing directive starts with the # character
    • The include directive tells the preprocessor to: extract all content in the <iostream> header file and provide it to the current file
    • The most common use of header files is to declare functions defined elsewhere
    • The function declaration informs the compiler how to call the function, and declares the number and types of parameters in the function, as well as the return type of the function. The function definition contains the actual code of the function. In C++, declarations are usually placed in files with the extension .h, called header files, and their definitions are usually contained in files with the extension .cpp, called source files
    • The standard library headers in C still exist in C++, but use the following two versions
      • Do not use the .h suffix, use the prefix c instead, such as <cstdio>, these versions are placed in the std namespace
      • Use the .h suffix, which is older versions like <stdio.h>, which don't use namespaces
    • Here is an example of using a preprocessor directive to avoid double inclusion
    #ifndef MYHEADER_H
    #define MYHEADER_H
    //...the contents of this header filef
    #endif
    // 或(上下等价)
    #pragma once
    //...the contents of this header filef
    
  • main() function

    • main() The entry point of a functional program, the explicit return statement can be ignored
    • argc gives the list of arguments passed to the program, and argv contains these arguments. Note that argv[0] may be the name of the program, or an empty string
  • I/O stream

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

1.2 Namespaces

  • Namespaces are used to handle name conflicts between different code segments
    // 头文件 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;
    }
    

Never use using directives or using-declarations in header files, or everyone who adds your header file will have to use it

  • C++17 allows convenient use of nested namespaces, that is, putting one namespace inside another
    namespace MyLibraries::Networking::FTP {
          
          }
    
  • C++17 also uses namespace aliases, which give another namespace a new, shorter name
    namespace MyFTP = MyLibraries::Networking::FTP;
    

1.3 Literals

  • Literals are used to write numbers or strings in code
  • The following literal specifies the number 123
    • Decimal literal 123
    • Octal literal 0173
    • Hex literal 0x7B
    • Binary literal 0b1111011
  • other literals
    • floating point value (eg 3.14f)
    • double precision floating point value (eg 3.14)
    • single character (eg 'a')
    • A zero-terminated character array (such as "character array")
    • You can also customize the argument type

1.4 Variables

  • In C++, variables can be declared anywhere, and a variable can be used anywhere after the line on which it is declared
  • When uninitialized variables are used in the code, most compilers will give a warning or error message
  • C++ common variable types
    insert image description here

insert image description here

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

1.5 Operators

insert image description here

1.6 Type

  • enumerated type

    • The enumerated type is just an integer value, the actual value of PieceTypeKing is 0
    • The compiler will issue a warning or error message if you attempt to perform arithmetic on a PieceType variable or treat it as an integer
    enum PieceType {
          
           
        PieceTypeKing,
        PieceTypeQueen,
        PieceTypeRook,
        PieceTypePawn
    };
    
  • Integer values ​​can also be specified for enumeration members

    • PieceTypeKing has an integer value of 1, the compiler assigns an integer value of 2 to PieceTypeQueen, PieceTypeRook has a value of 10, and the compiler automatically assigns a value of 11 to PieceTypePawn
    enum PieceType {
          
           
        PieceTypeKing = 1
        PieceTypeQueen,
        PieceTypeRook = 10
        PieceTypePawn
    };
    
  • strongly typed enum

    • The enum given above is not strongly typed, which means it is not type safe . They are always interpreted as integer data, so enumeration values ​​in completely different enumeration types can be compared. Strongly typed enum class enumerations solve these problems
    enum class PieceType {
          
          
        King = 1,
        Queen,
        Rook = 10,
        Pawn
    }
    
    • For enum classes, enumeration value names do not automatically go out of enclosing scope, which means always using the scope resolution operator
    PieceType piece = PieceType::King;
    
    • By default, the base type of an enumeration value is an integer, but this can be changed in the following ways
    enum class PieceType : unsigned long {
          
          
        King = 1,
        Queen,
        Rook = 10,
        Pawn
    }
    

    It is recommended to use type-safe enum class enumeration instead of type-unsafe enum enumeration

  • structure

    • A structure (struct) allows one or more existing types to be encapsulated into a new type
    // employeestruct.h
    #pragma once
    
    struct 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 Conditional Statements

  • if/else statement

    • The expression enclosed in the parentheses of the if statement must be a Boolean value, or the result of the evaluation must be a Boolean value
    • 0 is false, non-zero is true
    if (i > 4) {
          
          
        // Do something.
    } else if {
          
          
        // Do something else.
    } else {
          
          
        // Do something else.
    }
    
  • C++17 allows including an initializer in an if statement

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

    • The expression of the switch statement must be an integer, a type that can be converted to an integer, an enumeration type, or a strongly typed enumeration, and must be compared with a constant value. Each constant value represents a "case". If the expression pattern matches this case, subsequent lines of code will be executed until a break statement is encountered
    • In addition, a default case can be provided, and the expression value will match the default case if no other case matches the expression value
    switch(menuItem) {
          
          
        case OpenMenuItem:
            // Code to open a file
            break;
        case SaveMenuItem:
            // Code to save a file
            break;
        default:
            // Code to give an error message
            break;
    }
    
    • As with if statements, C++17 supports the use of initializers in switch statements
    switch (<initializer>; <expression>) {
          
          <body>}
    
  • conditional operator

    • Also called the ternary operator because it uses three operands
    // i 大于 2 吗?如果是真的,结果就是 yes,否则结果就是 no
    std::cout << ((i > 2) ? "yes" : "no");
    

1.8 Logical comparison operators

  • C++ uses short-circuit logic when evaluating expressions: this means that once the final result can be determined, the remainder of the expression is not evaluated
    insert image description here

1.9 Functions

  • In C++, in order for other code to use a function, the function should first be declared
    • If the function is used inside a particular file, it is usually declared and defined in the source file
    • If the function is intended to be used by other modules or files, it is common to declare the function in the header file and define the function in the source file
    // 函数声明
    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');
    
  • Inference of function return type
    • To use this feature, you need to specify auto as the return type, and the compiler infers the return type based on the expression used in the return statement
    auto addNumbers(int number1, int number2) {
          
          
        return number1 + number2;
    }
    
  • the name of the current function
    • Every function has a predefined local variable _func_ which contains the name of the current function. One use of this variable is for logging
    int addNumbers(int number1, int number2) {
          
          
        std::cout << "Entering function " << _func_ << std::endl;
        return number1 + number2;
    }
    

1.10 C-style arrays

  • In C++, C-style arrays should be avoided as much as possible, and std::array and std::vector in STL should be used instead
    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++ has a fixed-size special container std::array, which is defined in the <array> header file. Using std:array instead of C-style arrays will bring many benefits, as shown below
    • it always knows its own size
    • is not automatically converted to a pointer, thus avoiding certain types of bugs
    • Has an iterator for conveniently traversing elements
    array<int, 3> arr = {
          
          9, 8, 7};
    cout << "Array size = " << arr.size() << endl;
    cout << "2nd element = " << arr[1] << endl;
    

1.12 std::vector

  • If you want the size of the array to be dynamic, it is recommended to use std:vector. When new elements are added to the vector, the vector will automatically increase its size
    #include <vector> // 包含头文件
    
    vector<int> myVector = {
          
          11, 22};
    myVector.push_back(33); // 向 vector 中动态添加元素
    myVector.push_back(44);
    
    cout << "1st element: " << myVector[0] << endl;
    

1.13 Structured Binding

  • C++17 introduced the concept of structured bindings, which allow the declaration of multiple variables initialized with elements from an array, structure, pair, or tuple. For example, suppose you have the following array
    std::array<int, 3> values = {
          
          112233};
    
  • Three variables x, y, and z can be declared, initialized with the three values ​​in the subsequent array. Note that the auto keyword must be used for structured bindings
    auto [x, y, z] = values;
    
  • The number of variables declared with structured binding must match the number of values ​​in the right-hand expression

1.14 Loop

  • while loop

    • A while loop executes a block of code repeatedly as long as the conditional expression evaluates to true
    int i = 0;
    while (i < 5) {
          
          
        std::cout << "This is silly." << std::endl;
        ++i;
    }
    
  • do/while loop

    • The code will be executed once first, and the conditional check to determine whether to continue execution is placed at the end . You can use this loop version when you want a block of code to execute at least once, and depending on a condition whether to execute multiple times. The code below prints once despite the condition being false
    int i = 100;
    do {
          
          
        std::cout << "This is silly." << std::endl;
        ++i;
    } while (i < 5);
    
  • for loop

    • The syntax of the for loop is generally simpler, because you can see the initial expression of the loop, the end condition, and the statement executed after each iteration
    for (int i = 0; i < 5; ++i) {
          
          
        std::cout << "This is silly." << std::endl;
    }
    
  • range-based for loop

    • This kind of loop allows convenient iteration over the elements in a container, and can be used for C-style arrays, initializer lists, and also for types that have begin() and end() functions that return iterators: e.g. std::array, std:: vector
    std::array<int, 4> arr = {
          
          1, 2, 3, 4};
    for (int i : arr) {
          
          
        std::cout << i << std::endl;
    }
    

1.15 Initialization Lists

  • The initialization list is defined in the <initializer list> header file. With the initialization list, it is easy to write functions that can receive a variable number of parameters.
  • The initializer list class is a template that requires specifying the type of elements in the list between angle brackets, similar to specifying the type of object stored in a 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. Dig deep into C++

2.1 Strings in C++

  • There are three ways to use strings in C++
    • One is the C style, which treats characters as character arrays
    • One is C++ style, which encapsulates strings into an easy-to-use string type
    • There is also a non-standard common class (introduced in Chapter 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 Pointers and dynamic memory

  • Dynamic memory allows programs to be created with data of variable size at compile time, and most complex programs use dynamic memory in some way
2.2.1 Stack and Heap

Memory in a C++ program is divided into two parts: the stack and the heap

  • Stack (and stack means the same)

    • The stack is like a deck of cards, the current top card represents the current scope of the program, usually the currently executing function. All variables declared in the current function will occupy the memory of the top stack frame (that is, the topmost card) . If the current function foo() calls another function bar(), a new card will be turned over, so that bar() will have its own stack frame for it to run
    • Any arguments passed from foo() to bar() are copied from the foo() stack frame to the bar stack frame
    • A stack frame provides an independent memory space for each function. If a variable is declared on the foo() stack, calling the bar() function does not change that variable unless specifically requested
    • When the foo() function finishes executing, the stack disappears and all variables declared in the function no longer take up memory
    • Variables that allocate memory on the stack do not require the programmer to manually release the memory (delete), this process is done automatically
  • stack frame

    • A stack frame refers to the area (or space) allocated on the stack for the currently running function . The parameters passed in, the return address (which must be jumped to when the function ends), and the memory unit used by the function (that is, the local variables stored by the function on the stack) are all in the stack frame
    • Stack frames are usually created when a new function is called and destroyed when the function returns . To put it bluntly, the stack is composed of stack frames. Stack frames are pushed onto the stack when a function is called, and stack frames are popped from the stack when the function returns
  • heap

    • The heap is an area of ​​memory that has absolutely no relationship to the current function or stack frame . If you want to save the variables declared in the function call after the end, you can put the variables in the heap
    • The structure of the heap is not as complicated as the stack. The heap can be regarded as a pile of bits. The program can add new bits to the heap or modify existing bits in the heap at any time.
    • You must ensure that any memory allocated on the heap is freed (deleted), this process is not done automatically unless smart pointers are used
2.2.2 Using pointers
  • You can place anything on the heap by explicitly allocating memory. For example, to place an integer on the heap, memory needs to be allocated for it, but first a pointer needs to be declared
    • A pointer is just an address that points to an integer value . To access this value, the pointer needs to be dereferenced
    • You can think of dereferencing as looking for the actual value in the heap in the direction of the pointer arrow
    • The pointer must be valid before being dereferenced . Dereferencing a null or uninitialized pointer results in undetermined behavior
    // 应避免使用未初始化的变量/指针
    int *myIntegerPointer = new int; // 使用 new 操作符分配内存
    int *myIntegerPointer = nullptr; // 如果不希望立即分配内存,可以把它们初始化为空指针
    
  • assigns a value to a newly allocated integer on the heap
    *myIntegerPointer = 8; // 没改变指针,只是改变指针指向的内存
    
  • After using the dynamically allocated memory, you need to use the delete operator to release the memory. In order to prevent the pointer from being used after releasing the memory pointed to by the pointer, it is recommended to set the pointer to nullptr
    delete myIntegerPointer;
    myIntegerPointer = nullptr;
    
  • Pointers do not always point to heap memory, you can declare a pointer to a variable on the stack or even to other pointers
    • To make a pointer point to a variable, you need to use the "address" operator &
    int i = 8;
    int *myIntegerPointer = &i;
    
  • If the pointer points to a structure, you can first dereference the pointer with * and then use the normal . syntax to access the fields in the structure
    Employee *anEmployee = getEmployee();
    cout << (*anEmployee).salary << endl; // 同下等价
    cout << anEmployee->salary << endl; // -> 运算符允许同时对指针解除引用并访问字段
    
2.2.3 Dynamically allocated arrays
  • The heap can also be used to dynamically allocate arrays. Use the new[] operator to allocate memory for an array
    • As can be seen from the figure below: the pointer variable is still on the stack, but the dynamically created array is on the heap
    int arraySize = 8;
    int *myVariableSizedArray = new int[arraySize];
    

insert image description here

  • Now that memory has been allocated, use myVariableSizedArray as a normal stack-based array
    myVariableSizedArray[3] = 2;
    // 使用完这个数组后,应该将其从堆中删除,这样其他变量就可以使用这块内存
    delete[] myVariableSizedArray;
    myVariableSizedArray = nullptr;
    

Avoid malloc() and free() in C, use new and delete, or use new[] and delete[]

2.2.4 Null pointer variable
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 Smart Pointers
  • A smart pointer object automatically frees memory when it goes out of scope, for example after a function finishes executing . There are two most important smart pointers in C++

  • std::unique_ptr

    • Like a normal pointer, but automatically releases memory or resources when it goes out of scope or is deleted
    • unique_ptr only belongs to the object it points to
    • Pros: memory and resources are always freed (even when a return statement is executed or an exception is thrown)
    • create 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 can also be used to store C-style arrays
    auto employees = make_unique<Employee[]>(10);
    cout << "Salary: " << employees[0].salary << endl;
    
  • std::shared_ptr

    • shared ptr allows distributed "ownership" of data: each time shared ptr is specified, a reference count is incremented, indicating that the data has one more "owner". When the shared ptr goes out of scope, the reference count is decremented. When the reference count is 0, it means that the data no longer has any owner, so the object referenced by the pointer is released
    • create shared_ptr
    auto anEmployee = make_shared<Employee>();
    if (anEmployee) {
          
          
        cout << "Salary: " << anEmployee->salary << endl;
    }
    
    • Since C++17, arrays can also be stored in shared_ptr
    shared_ptr<Employee[]> employees(new Employee[10]);
    cout << "Salary: " << employees[0].salary << endl;
    

2.3 Multiple uses of const

2.3.1 Using const to define constants
  • Use const instead of #define to define constants
    const int versionNumberMajor = 2;
    const int versionNumberMinor = 1;
    const std::string productName = "Super Hyper Net Modulator";
    
2.3.2 Using const to protect parameters
void mysteryFunction(const std::string *someString) {
    
    
    *someString ="Test"; // 不允许修改
}

int main() {
    
    
    std::string myString = "The string";
    mysteryFunction(&myString);
    
    return 0;
}

2.4 References

  • Appending an & to a type indicates that the corresponding variable is a reference . Behind the scenes it's actually a pointer to the original variable. The variable x and the reference variable xReference point to the same value. If you change a value through one, the change will also be visible in the other
    int x = 42;
    int &xReference = x;
    
2.4.1 Pass by reference
  • Usually, when you pass variables to functions, you pass values. If the function takes an integer parameter, what is actually passed in is a copy of the integer, so the value of the original variable is not modified. Pointers in stack variables are commonly used in C to allow functions to modify variables in another stack frame
  • In C++, instead of passing pointers to functions, parameters are passed by reference
    // 不会影响传递给它的变量,因为变量是按照值传递的
    // 因此函数接收的是传递给它的值的一个副本
    void addOne(int i) {
          
          
        i++;
    }
    // 使用了引用,因此可以改变原始变量的值
    void addOne(int &i) {
          
          
        i++;
    }
    
2.4.2 Pass by const reference
  • When passing a value to a function, a full copy is made. When passing by reference, you're actually just passing a pointer to the original data , so you don't need to make a copy. By passing a const reference, you can achieve both: no copy is required, and the original variable is not modified
    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 Abnormal

#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; // 返回 5
    std::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 Type inference

2.6.1 Keyword auto
  • auto can be used to tell the compiler to automatically infer the type of the variable at compile time
  • But using auto removes the reference and const qualifiers
#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
  • The keyword decltype takes an expression as an actual parameter and calculates the type of the expression
    // 编译器推断出 y 的类型是 int,因为这是 x 的类型
    int x = 123;
    decltype(x) y = 456;
    
  • The difference between auto and decltype is that
    • decltype does not strip references and const qualifiers
    • f2 is defined with decltype as follows, causing f2 to be of type const string&, thus making no copies
    decltype(foo()) f2 = foo();
    

3. C++ as an object-oriented language

3.1 Define classes

  • In C++, a class is usually declared in a header file (.h) and its non-inline methods and static data members are defined in the corresponding source file (.cpp)
// AirlineTicket.h
#pragma once

#include <string>

class AirlineTicket {
    
    
public:
    AirlineTicket(); // 当创建类的对象时会自动调用构造函数
    ~AirlineTicket(); // 当销毁对象时会自动调用析构函数
    // 最好将不改变对象的任何数据成员的成员函数声明为 const
    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;
};
  • This definition first declares a class name, and declares the data members (attributes) and methods (behavior) of the class within curly braces
  • Each data member and method has a specific access level: public, protected, or private. These tokens can appear in any order and can be reused
    • public members are accessible outside the class
    • Private members cannot be accessed outside the class (it is recommended to declare all data members as private, and when needed, they can be accessed through public readers and setters)
3.1.1 Constructor initialization
  • Method 1: Constructor initializer
    AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),
                                     mNumberOfMiles(0),
                                     mHasEliteSuperRewardsStatus(false) {
          
          }
    
  • Method 2: Put the initialization task in the constructor body
    AirlineTicket::AirlineTicket() {
          
          
        mPassengerName = "Unknown Passenger";
        mNumberOfMiles = 0;
        mHasEliteSuperRewardsStatus = false;
    }
    
  • If the constructor is just to initialize the data members, there is actually no need to use the constructor , since the data members can be initialized directly in the class definition. If the class also needs to perform some other types of initialization, such as opening files, allocating memory, etc., you need to write a constructor to handle it
    private:
        std::string mPassengerName = "Unknown Passenger";
        int mNumberOfMiles = 0;
        bool mHasEliteSuperRewardsStatus = false;
    
3.1.2 Definition of some AirlineTicket class methods
// 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 Using classes

// 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 3
    delete myTicket3;  // delete the heap object!
    
    return 0;
} 

4. Uniform initialization

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;
};
  • Prior to C++11, the initialization methods for structures and classes were different
    CircleStruct myCirclel = {
          
          10102.5};
    CircleClass myCircle2(10102.5);
    
  • After C++11, it is allowed to use the {…} syntax to initialize types
    // 其中 = 号是可选的
    CircleStruct myCirclel = {
          
          10102.5};
    CircleClass myCircle2 = {
          
          10102.5};
    
  • Uniform initialization can also be used to initialize dynamically allocated arrays
int *pArray = new int[4]{
    
    0123};
  • Uniform initialization can also initialize class member arrays in constructor initializers
class MyClass {
    
    
public:
    MyClass() : mArray{
    
    0123} {
    
    }
private:
    int mArray[4];
};
  • Direct List Initialization vs. Copy List Initialization
    • Copy list initialization: T obj = {arg1, arg2, ...};
      • For copy-list-initialization, all elements of the initializer enclosed in curly braces must be of the same type
    • Direct list initialization: T obj {arg1, arg2, ...};

5. The first useful C++ program

  • Create an employee database

5.1 Employee Records System

  • A program for managing a company's employee records should be flexible and have efficient functions that include the following functions:

    • add employee
    • fire employees
    • employee promotion
    • View all employees, past and present
    • view all current employees
    • view all former employees
  • The code of the program is divided into three parts

    • The Employee class encapsulates information about a single employee
    • All employees of the Database class management company
    • Interfaces for individual user interface providers

5.2 Employee class

  • 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 employee
        void fire(); // Dismisses the employee
        void 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 class

  • 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 User Interface

  • 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;
    }
}

Guess you like

Origin blog.csdn.net/qq_42994487/article/details/131065119