c++大复习

发布于 2024-12-19  16 次阅读


c++大复习

CH1

构造函数和初始化

  你提到的 public: int x, y;​ 确实在类中声明了成员变量 x​ 和 y​。但这并不意味着它们在对象创建时会被自动初始化。让我们详细讨论一下这一点。

1. 声明与初始化的区别

  • 声明int x, y;​ 只是声明了变量,这意味着类的每个对象将包含两个整型变量 x​ 和 y​。但是,C++ 不会自动给这些变量赋初始值。
  • 默认值:如果你没有定义构造函数,x​ 和 y​ 的值将是未定义的,这可能导致不可预测的行为。

2. 构造函数的作用

  构造函数的主要作用是确保在对象创建时对成员变量进行初始化。构造函数可以:

  • 指定初始值:通过构造函数,你可以为 x​ 和 y​ 指定明确的初始值。
  • 提供灵活性:你可以定义多个构造函数,允许不同的初始化方式。

3. 示例

  考虑下面的例子:

class Point {
public:
    int x, y; // 仅声明,不初始化

    // 默认构造函数
    Point() {
        x = 0; // 初始化为0
        y = 0; // 初始化为0
    }

    // 带参数的构造函数
    Point(int a, int b) {
        x = a; // 使用参数初始化
        y = b; // 使用参数初始化
    }
};

int main() {
    Point p1; // 使用默认构造函数,x和y被初始化为0
    Point p2(10, 20); // 使用带参数的构造函数,x为10,y为20

    std::cout << "p1: (" << p1.x << ", " << p1.y << ")" << std::endl; // 输出: p1: (0, 0)
    std::cout << "p2: (" << p2.x << ", " << p2.y << ")" << std::endl; // 输出: p2: (10, 20)

    return 0;
}

  ‍

作用域和析构函数

  在 C++ 中,作用域是指一个变量的可见性和生命周期的范围。当一个变量超出其作用域时,该变量会被销毁,相关的资源也会被释放。对于类的对象,析构函数会在对象的生命周期结束时自动调用。下面详细解释这个过程。

1. 什么是作用域?

  • 作用域指的是变量的可见范围。在 C++ 中,常见的作用域有:

    • 全局作用域:在所有函数外部定义的变量。
    • 局部作用域:在函数或代码块内部定义的变量。

2. 对象的生命周期

  • 当你在某个作用域内创建一个对象(比如在函数内部),这个对象的生命周期与这个作用域相关联。
  • 当程序执行到作用域的结束(例如函数返回,或者代码块结束)时,所有在该作用域内创建的局部变量(包括对象)会被销毁。

3. 析构函数的自动调用

  • 当对象超出作用域时,C++ 会自动调用该对象的析构函数,以执行清理操作(如释放内存、关闭文件等)。
  • 这个过程是由 C++ 的内存管理机制自动处理的,程序员不需要手动调用析构函数。

示例

  考虑以下代码示例:

#include <iostream>
using namespace std;

class Buffer {
public:
    int* data;

    // 构造函数
    Buffer(int size) {
        data = new int[size]; // 动态分配内存
        cout << "Buffer allocated." << endl;
    }

    // 析构函数
    ~Buffer() {
        delete[] data; // 释放分配的内存
        cout << "Buffer deallocated." << endl;
    }
};

void createBuffer() {
    Buffer buf(10); // 在局部作用域内创建对象
    // buf在这里被创建并使用
    // 当函数结束时,buf将超出作用域
}

int main() {
    createBuffer(); // 调用函数
    // 一旦createBuffer函数结束,buf超出作用域,析构函数自动调用
    return 0;
}

解释

  • 在这个例子中,Buffer buf(10);​ 在 createBuffer​ 函数的局部作用域内创建。
  • createBuffer​ 函数执行完毕后,它的作用域结束,局部变量 buf​ 也随之超出作用域。
  • 这时,C++ 会自动调用 Buffer​ 类的析构函数,输出 Buffer deallocated.​,并释放 data​ 指向的内存。

总结

  • 超出作用域指的是对象生命周期结束,通常发生在函数返回或代码块结束时。
  • 自动调用析构函数是 C++ 内存管理的一部分,确保对象在生命周期结束时正确释放资源。

  ‍

cin 与 getline

1. getline(cin, str)

  • 使用:这是一个标准库函数,用于读取一整行输入,直到遇到换行符(\n​)。

  • 参数

    • cin​:输入流。
    • str​:用于存储读取到的字符串的标准字符串对象(std::string​)。
  • 特性

    • getline​ 会自动处理换行符,读取的字符串不包括换行符。
    • 它会动态调整存储的大小,适合读取任意长度的字符串。

  示例

#include <iostream>
#include <string>
using namespace std;

int main() {
    string str;
    cout << "Enter a line: ";
    getline(cin, str); // 读取整行
    cout << "You entered: " << str << endl;
    return 0;
}

2. cin.getline(str, 50)

  • 使用:这是 istream​ 类的成员函数,用于从输入流中读取固定长度的字符。

  • 参数

    • str​:用于存储读取到的字符串的字符数组(char[]​)。
    • 50​:指定读取的最大字符数(包括结束符)。
  • 特性

    • 如果输入超过指定长度,getline​ 将截断输入,并将剩余部分留在输入流中。
    • 读取的字符串会以 null 字符(\0​)结束。
    • 如果输入的字符数不足,str​ 会以 null 字符结束。

  示例

  cpp

  Copy

#include <iostream>
using namespace std;

int main() {
    char str[50];
    cout << "Enter a line: ";
    cin.getline(str, 50); // 读取最多49个字符
    cout << "You entered: " << str << endl;
    return 0;
}

3. 总结

  • getline(cin, str) ​:

    • 用于读取 std::string​ 类型,适合处理动态大小的字符串。
    • 读取整行输入,自动处理换行符。
  • cin.getline(str, 50) ​:

    • 用于读取 char​ 数组,适合处理固定大小的字符串。
    • 需手动指定最大字符数,且可能会截断输入。

使用场景

  • 使用 getline​ 更为常见,特别是在处理用户输入时,因为它更灵活且易于使用。
  • 使用 cin.getline​ 适合在需要限制输入长度的情况下,例如处理固定大小的字符数组。

  ‍

操作符重载

  操作符重载是 C++ 的一种特性,允许开发者为自定义类型(类)定义或修改已有操作符的行为。通过操作符重载,你可以使得自定义类型的对象像内置类型一样使用操作符,例如 +​、-​、*​、<<​ 和 >>​ 等。

1. 操作符重载的基本规则

  • 语法:操作符重载可以通过成员函数或友元函数实现。成员函数的第一个参数是 this ​指针,表示调用该操作符的对象;而友元函数则需要接受两个对象作为参数。
  • 返回值:通常返回一个新的对象,表示操作的结果。

2. 示例:重载加法运算符 (+​)

  下面是一个简单的示例,演示如何重载加法运算符:

  cpp

  Copy

#include <iostream>
using namespace std;

class Complex {
private:
    double real;
    double imag;

public:
    // 构造函数
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 操作符重载:加法
    Complex operator+(const Complex& other) {
        return Complex(real + other.real, imag + other.imag);
    }

    // 输出重载
    friend ostream& operator<<(ostream& os, const Complex& c) {
        os << c.real << " + " << c.imag << "i";
        return os;
    }
};

int main() {
    Complex c1(1.5, 2.5);
    Complex c2(3.5, 4.5);
    Complex c3 = c1 + c2; // 使用重载的加法运算符

    cout << "Result: " << c3 << endl; // 输出: Result: 5 + 7i
    return 0;
}

3. 常见的操作符重载

  以下是一些常见的可以重载的操作符:

  • 算术运算符:+​, -​, *​, /
  • 关系运算符:==​, !=​, <​, >​, <=​, >=
  • 逻辑运算符:&&​, ||​, !
  • 位运算符:&​, |​, ^​, <<​, >>
  • 索引运算符:[]
  • 函数调用运算符:()
  • 自增/自减运算符:++​, --

继承

  继承是面向对象编程(OOP)中的一个重要概念,允许一个类(称为子类或派生类)从另一个类(称为父类或基类)“继承”属性和方法。继承使得代码重用成为可能,并提供了一种建立类之间关系的方式。

1. 基本概念

  • 基类(父类) :提供属性和方法的类。
  • 派生类(子类) :从基类继承属性和方法的类,可以添加新属性和方法,或重写基类的方法。

2. 继承的好处

  • 代码重用:通过继承,子类可以重用父类的代码,减少重复。
  • 扩展性:可以在子类中添加新功能,而不需要修改基类。
  • 多态性:通过基类指针或引用可以指向派生类对象,允许在运行时决定调用哪个类的方法。

3. 继承的语法

  在 C++ 中,使用冒号(:​)来表示继承关系。基本语法如下:

class DerivedClass : public BaseClass {
    // 子类的成员
};

4. 示例

  以下是一个简单的例子,展示了继承的用法:

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    void speak() {
        cout << "Animal speaks" << endl;
    }
};

// 派生类
class Dog : public Animal {
public:
    void bark() {
        cout << "Dog barks" << endl;
    }
};

int main() {
    Dog dog;
    dog.speak(); // 调用基类的方法
    dog.bark();  // 调用子类的方法

    return 0;
}

5. 访问控制

  在 C++ 中,继承还可以通过访问控制符(public​、protected​、private​)来控制基类成员在派生类中的可见性:

  • public 继承:基类的 public 成员在派生类中仍然是 public。
  • protected 继承:基类的 public 和 protected 成员在派生类中变为 protected。
  • private 继承:基类的 public 和 protected 成员在派生类中变为 private。

6. 多重继承

  C++ 支持多重继承,即一个类可以继承多个基类:

class ClassA {};
class ClassB {};
class Derived : public ClassA, public ClassB {};

7. 总结

  • 继承允许创建新类时重用已有类的代码,提供了代码组织和扩展的灵活性。
  • 基类和派生类之间的关系使得可以建立更复杂的类层次结构,促进代码的可重用性和可读性。

  ‍

多态性和虚拟函数

1. 多态性

  多态性(Polymorphism)指的是同一个操作(方法或函数)可以作用于不同类型的对象,并产生不同的结果。在 C++ 中,多态性主要分为两种类型:

  • 编译时多态性(静态多态性):通过函数重载和运算符重载实现。
  • 运行时多态性(动态多态性):通过虚拟函数和继承实现。

2. 虚拟函数

  虚拟函数(Virtual Functions)是 C++ 中的一种机制,用于实现运行时多态性。虚拟函数允许在基类中声明一个函数,并在派生类中重写(override)该函数。通过基类指针或引用调用虚拟函数时,会根据实际对象的类型决定调用哪个版本的函数。

虚拟函数的特点

  • 在基类中使用 virtual​ 关键字声明。
  • 在派生类中可以重写虚拟函数。
  • 通过基类指针或引用调用虚拟函数时,会执行实际对象的函数,而不是指针或引用的类型所指向的函数。

3. 示例代码

  以下是一个使用虚拟函数实现多态性的示例:

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    virtual void speak() { // 声明虚拟函数
        cout << "Animal speaks" << endl;
    }
};

// 派生类1
class Dog : public Animal {
public:
    void speak() override { // 重写虚拟函数
        cout << "Dog barks" << endl;
    }
};

// 派生类2
class Cat : public Animal {
public:
    void speak() override { // 重写虚拟函数
        cout << "Cat meows" << endl;
    }
};

int main() {
    Animal* animal1 = new Dog(); // 基类指针指向派生类对象
    Animal* animal2 = new Cat(); // 基类指针指向另一个派生类对象

    animal1->speak(); // 输出: Dog barks
    animal2->speak(); // 输出: Cat meows

    delete animal1; // 释放内存
    delete animal2; // 释放内存

    return 0;
}

4. 代码解释

  • 基类 Animal​:

    • 定义了一个虚拟函数 speak()​,表示动物的叫声。
  • 派生类 Dog Cat​:

    • 分别重写了 speak()​ 函数,提供各自的实现。
  • 主函数 main​:

    • 创建了 Animal​ 类型的指针,分别指向 Dog​ 和 Cat​ 对象。
    • 调用 speak()​ 函数时,程序根据指针实际指向的对象类型执行对应的函数,表现出多态性。

5. 总结

  • 多态性使得同一操作可以作用于不同类型的对象,提高了代码的灵活性和可扩展性。
  • 虚拟函数是实现运行时多态性的关键机制,允许在基类中定义接口,并在派生类中提供具体实现。

  在 C++ 中,即使派生类重写了基类的虚拟函数,基类仍然需要定义该函数。这样做有几个重要的理由:

1. 提供默认行为

  在基类中定义虚拟函数可以提供一个默认实现。这样,如果某个派生类没有重写这个函数,仍然可以调用基类的实现。这在某些情况下是非常有用的,比如:

  • 简单派生类:有些派生类可能不需要提供特定的实现,直接使用基类的默认行为即可。
  • 调试:在调试时,可以使用基类的实现来检查调用流程。

2. 确保接口一致性

  通过在基类中定义虚拟函数,可以确保所有派生类都有相同的接口。这种一致性使得代码更易于理解和维护。例如,如果你有一个函数接受基类指针或引用作为参数,它可以调用 speak()​ 方法,而不需要知道具体是哪种动物:

void makeAnimalSpeak(Animal* animal) {
    animal->speak(); // 调用基类的接口
}

3. 实现多态性

  虚拟函数的存在使得多态性得以实现。通过基类指针或引用调用虚拟函数时,会根据实际对象的类型决定调用哪个实现。即使派生类重写了该函数,基类中仍然定义的虚拟函数是多态机制的一部分。

  ‍

非静态成员函数

  非静态成员函数是属于类的实例(对象)的函数,而不是属于类本身的函数。以下是一些关键点:

  1. 与对象关联:非静态成员函数需要通过对象来调用。这意味着它们可以访问该对象的非静态成员变量和其他非静态成员函数。

  2. 隐式参数:在非静态成员函数中,隐式地有一个指向调用该函数的对象的指针(通常是 this​ 指针),允许函数访问该对象的属性和其他方法。

  3. 实例化:要调用非静态成员函数,必须先创建类的实例。例如:

    class MyClass {
    public:
        void MyFunction() {
            // 访问对象的成员变量
        }
    };
    
    MyClass obj; // 创建对象
    obj.MyFunction(); // 调用非静态成员函数
    
  4. 与静态成员函数的区别:静态成员函数属于类本身,可以直接通过类名调用,而不需要创建对象。静态成员函数不能访问非静态成员变量或非静态成员函数。

  非静态成员函数通常用于需要操作特定对象状态的场景。

  ‍

为什么虚函数必须是非静态的?

  1. 非静态成员函数

    • 非静态成员函数是与对象实例相关联的。它们可以访问该对象的成员变量(使用 this​ 指针)。
    • 当你创建一个对象时,可以通过这个对象调用它的成员函数。
  2. 静态成员函数

    • 静态成员函数是与类本身相关联的,而不是与任何特定的对象实例相关联。
    • 静态函数不能使用 this​ 指针,因为它们不属于任何对象。
    • 由于没有 this​ 指针,静态成员函数无法访问对象的实例数据,也不能实现多态性。

作用域解析运算符::

  ​::​ 是作用域解析运算符(scope resolution operator),用于指定标识符的作用域。它有几个主要用途:

  1. 访问类的成员:用于访问类中的静态成员或嵌套类。例如:

    class MyClass {
    public:
        static int value;
    };
    
    int MyClass::value = 10; // 定义静态成员
    
  2. 访问命名空间中的成员:用于访问特定命名空间中的变量或函数。例如:

    namespace MyNamespace {
        void MyFunction() {
            // ...
        }
    }
    
    MyNamespace::MyFunction(); // 调用命名空间中的函数
    
  3. 全局作用域:在全局作用域中使用 ::​ 可以访问全局变量或函数,避免与局部作用域中的同名标识符冲突。例如:

    int value = 5; // 全局变量
    
    void MyFunction() {
        int value = 10; // 局部变量
        std::cout << ::value; // 输出全局变量
    }
    
  4. 类的构造函数和析构函数:在定义类的构造函数和析构函数时,使用 ::​ 来指定它们属于哪个类。例如:
    cpp

    Copy

    class MyClass {
    public:
        MyClass() { /* 构造函数 */ }
        ~MyClass() { /* 析构函数 */ }
    };
    
    MyClass::MyClass() { /* 实现 */ } // 实现构造函数
    

  通过使用 ::​ 运算符,可以明确指明所引用的标识符的作用域,避免歧义。

new

1. 不使用 new的情况

  • 栈上分配:如果在 example()​ 函数中声明一个 Son​ 对象,比如 Son A;​,这个对象存储在栈上。
  • 作用域:当 example()​ 函数执行完毕时,A​ 的生命周期结束,资源会自动释放。此时,A​ 不能再在 main()​ 函数中访问或修改,因为它已经被销毁了。

2. 使用 new的情况

  • 堆上分配:如果使用 new​ 创建对象,比如 Son* B = new Son();​,这个对象存储在堆内存中。
  • 持续存在:堆上的对象在程序运行时存在,直到显式调用 delete​。这样,即使在 example()​ 函数结束后,你仍然可以通过指针在 main()​ 函数中访问和修改这个对象。

示例代码

#include <iostream>

class Son {
public:
    void show() { std::cout << "Son object\n"; }
};

void example() {
    Son A; // 在栈上创建,生命周期仅限于 example 函数
    A.show(); // 可以在这里调用
    // A 的生命周期结束后,自动被销毁
  
    Son* B = new Son(); // 在堆上创建,生命周期由手动管理
    B->show(); // 在这里调用
    // 注意:B 需要在不再使用时手动 delete
}

int main() {
    example();
  
    // 这里不能访问 A,因为它已经被销毁
    // A.show(); // 错误,A 不再有效

    Son* B = new Son(); // 重新在 main 中创建
    B->show(); // 可以调用
    delete B; // 释放内存

    return 0;
}

总结

  • 不加 new​:对象只能在其创建的函数中使用,超出作用域后自动销毁。
  • new​:对象可以在多个函数中使用,直到显式释放内存,允许更灵活的内存管理。

1. 静态成员的特性

  • 静态成员变量 是属于类本身的,而不是属于某个具体的对象。这意味着它们在内存中的存储是全局的,所有对象共享同一份数据。
  • 静态成员变量在类的外部定义和初始化,而不是在类的内部或对象的上下文中。

2. 使用 new​ 的问题

  • 当你使用 new int counter;​ 时,你是在动态分配内存,并创建一个新的 int​ 类型的变量。这样做会导致每个对象都有自己独立的 counter​ 变量,不再是共享的静态成员。

  • 因为 counter​ 是静态的,应该在类的外部进行定义和初始化,如:
    cpp

    Copy

    int Car::counter = 0; // 在类外进行初始化
    

指针

  • A *const p​:指针不变,内容可变。
  • const A *const p​:指针不变,内容不可变。
  • const A *p​:指针可变,内容不可变。

  ‍

虚拟继承

例子:虚拟继承的实现

  下面是一个功能完整的示例,展示了如何使用虚拟继承:

  cpp

  Copy

#include <iostream>

// 基类
class Base {
public:
    Base() { std::cout << "Base constructor called\n"; }
    void showValue() { std::cout << "Value: " << value << "\n"; }
    int value = 42;
};

// 虚拟继承的派生类
class Derived1 : public virtual Base {
public:
    Derived1() { std::cout << "Derived1 constructor called\n"; }
};

class Derived2 : public virtual Base {
public:
    Derived2() { std::cout << "Derived2 constructor called\n"; }
};

// 最终派生类
class MostDerived : public Derived1, public Derived2 {
public:
    MostDerived() { std::cout << "MostDerived constructor called\n"; }
};

int main() {
    MostDerived obj;  // 创建 MostDerived 对象
    obj.showValue();  // 访问 Base 的方法

    return 0;
}

输出

  运行以上代码会得到如下输出:

Base constructor called
Derived1 constructor called
Derived2 constructor called
MostDerived constructor called
Value: 42

解释

  1. 构造顺序

    • 当创建 MostDerived​ 对象时,首先调用基类 Base​ 的构造函数,接着是 Derived1​ 和 Derived2​ 的构造函数,最后是 MostDerived​ 的构造函数。
    • 由于使用了虚拟继承,Base​ 只被构造一次。
  2. 只有一个 Base实例

    • 虚拟继承确保 MostDerived​ 只会有一个 Base​ 的实例。无论 Derived1​ 和 Derived2​ 各自如何继承 Base​,在 MostDerived​ 中,只有一个 Base​ 对象存在。
    • MostDerived​ 通过 Derived1​ 和 Derived2​ 访问 Base​ 的成员,但实际上它访问的是同一个 Base​ 实例。
  3. 功能展示

    • 通过调用 obj.showValue()​,可以访问 Base​ 的成员,显示 value​ 的值,证明 MostDerived​ 确实拥有一个有效的 Base​ 实例。

总结

  • 虚拟继承允许派生类共享基类的单一实例,避免冗余和二义性。
  • MostDerived​ 中,Base​ 的实例来自于 Derived1​ 和 Derived2​,但由于虚拟继承,它们共享同一个实例。

  ‍

抽象类

  抽象类是 C++ 中的一种特殊类,主要用于定义接口和规范。以下是关于抽象类的一些关键点:

1. 定义

  • 抽象类 是至少包含一个 纯虚函数 的类。纯虚函数是一种在基类中声明但不提供实现的虚函数,形式为 virtual void functionName() = 0;​。

2. 特点

  • 不能实例化:抽象类不能直接创建对象。你不能像其他类那样实例化抽象类。
  • 用于派生:通常,抽象类是用作其他类的基类(派生类)。派生类必须实现所有的纯虚函数才能成为具体类(可以实例化的类)。

3. 用途

  • 定义接口:抽象类可以定义一个公共接口,确保所有派生类都实现特定的功能。
  • 代码重用:可以在抽象类中实现一些通用的功能,派生类可以直接使用。

4. 示例

  cpp

  Copy

class Shape { // 抽象类
public:
    virtual void draw() = 0; // 纯虚函数
};

class Circle : public Shape { // 派生类
public:
    void draw() override { // 实现纯虚函数
        // 绘制圆的代码
    }
};

class Square : public Shape { // 另一个派生类
public:
    void draw() override { // 实现纯虚函数
        // 绘制方的代码
    }
};

int main() {
    Shape* shape1 = new Circle(); // 创建 Circle 对象
    shape1->draw(); // 调用 Circle 的 draw 方法

    Shape* shape2 = new Square(); // 创建 Square 对象
    shape2->draw(); // 调用 Square 的 draw 方法

    delete shape1;
    delete shape2;
    return 0;
}

5. 总结

  • 抽象类是一种用于定义接口的工具,不能直接实例化,但可以通过派生类实现具体的功能。它帮助程序员在设计类时保持一致性和可扩展性。

另一个示例:使用抽象类的引用

  cpp

  Copy

#include <iostream>
using namespace std;

// 抽象类
class Shape {
public:
    virtual void draw() = 0; // 纯虚函数
};

// 派生类:圆形
class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing a Circle" << endl;
    }
};

// 派生类:正方形
class Square : public Shape {
public:
    void draw() override {
        cout << "Drawing a Square" << endl;
    }
};

// 函数,接受 Shape 的引用
void renderShape(Shape& shape) {
    shape.draw(); // 调用具体的绘制方法
}

int main() {
    Circle circle;
    Square square;

    // 使用引用来调用
    renderShape(circle); // 输出 "Drawing a Circle"
    renderShape(square); // 输出 "Drawing a Square"

    return 0;
}

函数定义及调用

  • 在函数 renderShape(Shape& shape)​ 中,Shape&​ 表示参数 shape​ 是一个对 Shape​ 类型的引用。这意味着你可以直接传递一个 Shape​ 类型的对象(或其派生类的对象),而无需使用指针。
  • 当调用 renderShape(circle)​ 时,circle​ 是 Circle​ 类的对象。由于 Circle​ 继承自 Shape​,你可以将 circle​ 对象传递给 renderShape​ 函数。C++ 会自动将 circle​ 对象的引用传递给 shape​ 参数。

示例分析

  cpp

  Copy

void renderShape(Shape& shape) {
    shape.draw(); // 调用具体的绘制方法
}
  • 这里 shape​ 是一个引用,指向传入的对象(例如 circle​ 或 square​)。
  • 由于 shape​ 是一个引用,可以直接调用 draw()​ 方法,而不需要使用 ->​ 运算符。

总结

  • 使用引用的好处是让代码更简洁,避免了指针的复杂性,同时也避免了指针可能出现的空指针问题。
  • 通过引用,你可以像使用普通对象一样使用形参,而不需要明确地解引用它。

  因此,renderShape(circle)​ 可以直接使用对象 circle​,而不是使用指针。这使得代码更易于阅读和维护。

三元运算符的语法

condition ? expression1 : expression2

组成部分

  1. condition(条件):

    • 这是一个布尔表达式,返回 true​ 或 false​。
    • 例如,a > b​。
  2. expression1(真值表达式):

    • 如果条件为 true​,则返回这个表达式的值。
    • 例如,a​。
  3. expression2(假值表达式):

    • 如果条件为 false​,则返回这个表达式的值。
    • 例如,b1​。

文档类和视图类

  在MFC(Microsoft Foundation Classes)中,文档类和视图类是实现应用程序的核心组件,它们之间的关系是基于MVC(Model-View-Controller)设计模式的。以下是它们之间的主要关系和功能:

1. 文档类(CDocument)

  • 定义:文档类负责管理应用程序的数据和状态。它通常包含应用程序的核心数据结构和逻辑。

  • 功能

    • 数据管理:存储和管理应用程序的数据(如文件内容、用户输入等)。
    • 数据持久化:处理数据的读写,包括文件的打开、保存和关闭。
    • 更新通知:在数据发生变化时,通知视图类更新显示。

2. 视图类(CView)

  • 定义:视图类负责显示文档类中的数据。每个文档可以有多个视图,视图类渲染文档数据并处理用户输入。

  • 功能

    • 数据展示:将文档中的数据以图形或文本的形式展现给用户。
    • 用户输入处理:响应用户的操作(如鼠标点击、键盘输入等),并可能更新文档中的数据。
    • 更新显示:根据文档的变化,更新自身显示的内容。

3. 互相关系

  • 一对多关系:一个文档类对象通常可以对应多个视图类对象。这样,用户可以通过不同的视图来查看同一份文档数据。

  • 消息传递

    • 当文档中的数据发生变化时,文档类使用UpdateAllViews​方法通知所有关联的视图类进行更新。
    • 视图类在处理用户输入时,可以通过调用文档类的方法来修改数据。

  ‍


人生到处知何似,应似飞鸿踏雪泥。