C++ 应用开发层(Application Layer)

发布于:2024-07-11 ⋅ 阅读:(36) ⋅ 点赞:(0)

1.C 和 C++ 最广泛的区别总结

C 和 C++ 的主要区别在于编程范式和语言特性。C 作为结构化编程语言,主要强调过程和算法;而 C++ 则引入了面向对象编程,增加了封装、继承和多态的特性,使代码更具灵活性和复用性。C++ 支持函数重载、虚函数、模板编程和异常处理,并提供了丰富的标准库和高级的输入输出流管理,使编程更高效便捷。C++ 还引入了命名空间、智能指针、构造函数和析构函数,支持多重继承和复杂的类型系统,使内存管理更安全,代码更易于维护和扩展。

1. 编程范式

  • C:结构化编程,强调过程和算法。
  • C++:面向对象编程,强调对象模型和类,同时支持结构化编程。

2. 语言特性

  • C:注重过程和算法。
  • C++:在此基础上增加了封装、继承和多态三大面向对象特性。

3. 函数和方法

  • C:不支持函数重载和虚函数。
  • C++:支持函数重载和虚函数,通过虚函数表实现多态。

4. 动态内存管理

  • C:使用 mallocfree 进行内存管理。
  • C++:增加了 newdelete,进行更安全和方便的内存管理。

5. 模板和泛式编程

  • C:不支持模板和泛式编程。
  • C++:支持模板,使得编写泛型代码更加容易。

6. 标准库

  • C:标准库较为基础。
  • C++:提供了丰富的 STL,包括容器、算法、迭代器等。

7. 错误处理

  • C:使用返回值和 errno
  • C++:引入了异常处理机制,如 try, catch, throw

8. 输入输出

  • C:使用 printfscanf 进行输入输出。
  • C++:使用 cincout 进行输入输出,本质上是输入输出流,同时支持更高级的输入输出流,如 fstream 用于文件操作。

9. 命名空间

  • C:不支持命名空间。
  • C++:引入命名空间,用于组织和管理代码,避免命名冲突。

10. 智能指针

  • C:不支持智能指针。
  • C++:C++11 引入智能指针,如 std::unique_ptrstd::shared_ptr,更好地管理内存,防止内存泄漏和悬空指针。

11. 构造函数和析构函数

  • C:不支持构造函数和析构函数。
  • C++:引入构造函数和析构函数用于对象的初始化和清理。

2.C++ 和 Java 区别总结

1. 编程范式

  • C++:支持面向对象编程和结构化编程。
  • Java:完全面向对象编程。

2. 指针

  • C++:支持指针,允许直接操作内存。
  • Java:不支持指针,内存管理由虚拟机自动处理。

3. 多重继承

  • C++:支持多重继承。
  • Java:不支持多重继承,但支持一个类实现多个接口。

4. 自动内存管理

  • C++:手动管理内存,使用 newdelete
  • Java:自动内存管理,使用垃圾回收机制。

5. 操作符重载

  • C++:支持操作符重载。
  • Java:不支持操作符重载。

6. 预处理功能

  • C++:支持预处理器,宏定义、文件包含等。
  • Java:不支持预处理器,但使用 import 进行包管理。

7. 类型转换

  • C++:支持隐式和显式类型转换。
  • Java:需要显式的强制类型转换。

8. 字符串

  • C++:使用 Null 终止符表示字符串结束。
  • Java:使用 StringStringBuffer 类表示字符串。

9. Goto 语句

  • C++:支持 goto 语句,但不推荐使用。
  • Java:不支持 goto 语句,尽管是保留关键字。

10. 异常机制

  • C++:支持异常处理机制(try, catch, throw)。
  • Java:支持异常处理机制,增强系统容错能力。

11. 输入输出

  • C++:使用 printfscanf,以及面向对象的 cincout 流操作。
  • Java:使用 System.out.printSystem.in.read,主要通过流进行操作。

12. 智能指针

  • C++:C++11 引入智能指针(std::unique_ptr, std::shared_ptr)管理内存。
  • Java:自动内存管理,不需要智能指针。

13. 构造函数和析构函数

  • C++:支持构造函数和析构函数用于对象的初始化和清理。
  • Java:支持构造函数,没有显式的析构函数,由垃圾回收机制管理对象的销毁。

3.面向对象的三大特性

C++ 语言的面向对象编程(OOP)有三大特性:封装、继承、多态。以下是对这三大特性的详细说明。

1. 封装

概念: 封装是将数据和操作这些数据的代码捆绑在一起,并对外部世界隐藏实现细节的一种方法。通过封装,类可以控制哪些数据和方法对外部可见,哪些是私有的。封装确保了对象的完整性和安全性,使得对象的使用者不需要了解内部实现细节。

详细说明

  • 封装通过访问控制(如 private, protected, public)来实现数据的隐藏和保护。
  • 公有接口(public 方法)提供了与对象交互的方式,而私有成员(private 数据和方法)则保护了对象的内部状态。
  • 通过封装,可以防止对象的内部状态被意外或错误地修改,增强代码的安全性和可维护性。

2. 继承

概念: 继承是从已有类创建新类的机制。通过继承,新类(派生类)可以继承已有类(基类)的属性和方法,并可以扩展或重写这些属性和方法。继承的方式(如公有、保护、私有继承)决定了基类成员在子类中的可见性和访问权限。

详细说明

  • 继承实现了代码重用和层次分类,通过继承,派生类可以复用基类的代码。
  • 派生类可以通过重写基类的虚函数来提供自己的实现,增强或改变基类的方法。
  • 继承的方式(publicprotectedprivate 继承)控制了基类成员在派生类中的可见性:
    • 公有继承(public):基类的公有成员在派生类中仍然是公有的,保护成员在派生类中仍然是保护的。
    • 保护继承(protected):基类的公有成员和保护成员在派生类中都变成保护的。
    • 私有继承(private):基类的所有成员在派生类中都变成私有的。

3. 多态

概念: 多态性是指同一个操作在不同对象上的不同表现形式。多态分为编译时多态(如函数重载和运算符重载)和运行时多态(如通过基类指针调用派生类重写的虚函数)。

详细说明

  • 编译时多态:通过函数重载和运算符重载实现,函数根据参数类型和数量的不同实现不同的功能。
  • 运行时多态:通过虚函数实现,基类指针或引用在运行时可以调用派生类重写的虚函数。
    • 虚函数通过虚函数表(vtable)和虚函数表指针(vptr)实现,编译器在编译时创建虚函数表,在运行时通过虚函数表指针调用实际的函数实现。
    • 当基类指针或引用指向派生类对象时,调用虚函数会动态绑定到派生类的实现上,实现在运行时确定实际调用的函数。

4.类和数据抽象总结

1. 类的关系

组合

  • 定义:一个类的数据成员是另一个类的对象。组合类是当前类,它包含其他类的对象作为其成员。
  • 创建过程:先调用内嵌对象(成员对象)的构造函数,再调用组合类(当前类)的构造函数。
  • 析构过程:先执行组合类(当前类)的析构函数,再按内嵌对象(成员对象)成员的定义顺序逆序调用内嵌对象的析构函数。这意味着析构函数也是从内向外调用,即从成员对象开始,逐步到组合类。

使用

  • 定义:一个类通过参数、函数调用等方式使用另一个类的对象。
  • 创建过程:使用对象的创建通常在需要时进行,不直接影响当前类的创建过程。
  • 析构过程:使用对象在超出作用域或不再需要时自动销毁,不影响当前类的析构过程。使用对象的析构是根据其作用域决定的。

继承

  • 定义:一个类通过继承另一个类的属性和方法,形成父类和子类的关系。
  • 创建过程:先调用父类的构造函数,再调用子类的构造函数。
  • 析构过程:先执行子类的析构函数,再按继承链顺序逆序调用父类的析构函数。这意味着析构函数是从最派生的类开始,逐层向基类调用。

2. 三种继承方式

  • 公有继承:父类的公有成员在子类中保持公有,保护成员在子类中保持保护。
  • 保护继承:父类的公有成员和保护成员在子类中都变为保护。
  • 私有继承:父类的公有成员和保护成员在子类中都变为私有。

3.C++ 中重载、重写和重定义的区别

1. 重载(Overload)
  • 定义:在同一作用域中,具有相同名称但参数列表不同的一组函数。
  • 特点
    • 参数列表必须不同,可以是参数类型、参数个数或参数顺序不同。
    • 重载函数的返回类型可以相同也可以不同,但返回类型不能用于区分重载函数。
    • 函数名相同但参数列表不同的函数。
  • 用途:提供多个同名函数,以处理不同类型或数量的参数。
  • 示例
    #include <iostream>
    
    class Base {
    public:
        // 基类中的虚函数
        virtual void func(int a) {
            std::cout << "Base func(int): " << a << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        // 派生类重写基类的虚函数
        void func(int a) override {
            std::cout << "Derived func(int): " << a << std::endl;
        }
    
        // 派生类重载基类的虚函数
        void func(double a) {
            std::cout << "Derived func(double): " << a << std::endl;
        }
    
        // 派生类重载基类的虚函数
        void func(int a, double b) {
            std::cout << "Derived func(int, double): " << a << ", " << b << std::endl;
        }
    };
    
    int main() {
        Derived d;
    
        // 调用 Derived 中重写的函数
        d.func(10);              // 输出:Derived func(int): 10
    
        // 调用 Derived 中重载的函数
        d.func(3.14);            // 输出:Derived func(double): 3.14
        d.func(10, 3.14);        // 输出:Derived func(int, double): 10, 3.14
    
        // 通过基类指针调用函数
        Base* b = &d;
        b->func(10);             // 输出:Derived func(int): 10  (多态性)
    
        return 0;
    }
    
    (1)为什么前置自加需要返回引用,而后置自加不需要?
  • 前置自加

    • 直接修改对象并返回修改后的对象本身。
    • 返回引用避免了不必要的对象拷贝,提升性能。
    • 允许链式调用(如 ++(++x))。
    • Counter& operator++() {
          ++value;    // 先增加对象的值
          return *this;  // 返回对象本身的引用
      }
      
  • 后置自加

    • 返回操作前的状态,因此必须创建一个临时对象。
    • 临时对象是一个值,返回后不会影响原始对象。
    • 返回值不能直接影响链式调用,需要额外的临时存储
    • Counter operator++(int) {
          Counter temp = *this;  // 保存当前对象的副本
          ++value;    // 增加对象的值
          return temp;  // 返回保存的副本
      }
      

(2)提取运算符与插入运算符重载
  • 插入运算符重载

    • 定义为友元函数,接受 std::ostream 引用和 Complex 对象引用作为参数。
    • 使用 ostream 对象将 Complex 对象的 realimag 成员输出到流中。
  • 提取运算符重载

    • 定义为友元函数,接受 std::istream 引用和 Complex 对象引用作为参数。
    • 使用 istream 对象从流中提取 Complex 对象的 realimag 成员。
/*    os 是提取运算符(<<)的调用对象,表示从哪个输入流读取数据。
c 是提取运算符的目标对象,表示读取的数据将存储到该对象中。    */

    // 重载插入运算符 <<
    friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
        os << c.real << " + " << c.imag << "i";
        return os;
    }

/*    is 是提取运算符(>>)的调用对象,表示从哪个输入流读取数据。
c 是提取运算符的目标对象,表示读取的数据将存储到该对象中。    */

    // 重载提取运算符 >>
    friend std::istream& operator>>(std::istream& is, Complex& c) {
        is >> c.real >> c.imag;
        return is;
    }
2. 重写(Override)
  • 定义:在派生类中重新定义基类中的虚函数。
  • 特点
    • 被重写的函数必须是虚函数(virtual),并且派生类中的函数必须具有相同的函数签名(参数列表和返回类型)。
    • 重写的函数名和参数列表必须与基类中的虚函数完全相同。
    • 基类中的虚函数在派生类中被重新定义以提供不同的实现。
  • 用途:提供多态行为,通过基类指针或引用调用派生类的方法。
  • 示例
    class Base {
    public:
        virtual void func() {}
    };
    
    class Derived : public Base {
    public:
        void func() override {} // 重写基类的虚函数
    };
    
3. 重定义(隐藏)(Redefine/Hide)
  • 定义:在派生类中定义与基类中名称相同但非虚的函数。
  • 特点
    • 重定义的函数在基类中不是虚函数。
    • 派生类中的函数隐藏基类中的同名函数,无论参数列表是否相同。
    • 如果基类中的函数是非虚函数,派生类中定义的同名函数会隐藏基类中的函数。
    • 参数列表和返回类型可以不同。
  • 用途:隐藏基类中的同名函数,提供新的实现。
  • 示例
    #include <iostream>
    
    class Base {
    public:
        void func() {
            std::cout << "Base func()" << std::endl;
        }
    
        void func(int x) {
            std::cout << "Base func(int)" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void func() {
            std::cout << "Derived func()" << std::endl;
        }
    
        void func(double y) {
            std::cout << "Derived func(double)" << std::endl;
        }
    };
    
    int main() {
        Derived d;
        d.func();            // 调用 Derived::func()
        d.func(3.14);        // 调用 Derived::func(double)
        d.Base::func(10);    // 调用 Base::func(int)
        return 0;
    }
    

    4.析构函数

(1)析构函数的作用

析构函数在对象的生命周期结束时被调用,主要用于清理和释放对象占用的资源。其主要作用包括:

  1. 释放动态分配的内存:如果对象在构造函数或成员函数中动态分配了内存,析构函数应该负责释放这些内存,以防止内存泄漏。
  2. 关闭文件或网络连接:如果对象打开了文件或建立了网络连接,析构函数应该负责关闭这些资源。
  3. 释放其他系统资源:如锁、线程或其他系统资源。
(2)析构函数的特点
  1. 名称:析构函数与类名相同,但前面加上波浪号 ~
  2. 没有参数和返回值:析构函数不能有参数,也没有返回值。
  3. 不能重载:一个类只能有一个析构函数。
  4. 自动调用:当对象超出其作用域或显式删除对象时,编译器自动调用析构函数。

5.构造函数和析构函数的执行顺序

(1)构造函数的执行顺序
  1. 基类构造函数:首先调用基类的构造函数。如果有多个基类,则按类派生表中出现的顺序依次调用。
  2. 成员类对象构造函数:然后调用成员类对象的构造函数。如果有多个成员类对象,则按它们在类中声明的顺序依次调用。
  3. 派生类构造函数:最后调用派生类的构造函数。
(2)析构函数的执行顺序
  1. 派生类析构函数:首先调用派生类的析构函数。
  2. 成员类对象析构函数:然后调用成员类对象的析构函数。如果有多个成员类对象,则按它们在类中声明的顺序相反的顺序依次调用。
  3. 基类析构函数:最后调用基类的析构函数。

6.纯虚函数

纯虚函数用于面向对象编程中的接口继承和实现继承,允许派生类根据需要继承接口和/或实现。

(1)定义
  • 纯虚函数:在基类中声明但不实现,语法为 virtual void function() = 0;

        作用:纯虚函数的存在表明基类提供了一个接口,要求所有派生类必须实现这个函数。基类只是定义了函数的接口,而没有提供具体的实现。这种设计用于确保派生类必须提供自己的实现,从而实现多态性。

  • 抽象类

    定义:抽象类是包含一个或多个纯虚函数的类。由于纯虚函数没有实现,因此抽象类不能实例化对象。

    作用:抽象类提供了一个框架,定义了派生类必须实现的接口。它们用于定义派生类的公共接口,并且无法创建抽象类的对象,因为其接口不完整。

(2)应用
  1. 接口继承:纯虚函数仅提供接口,派生类必须实现这些函数才能实例化。
  2. #include <iostream>
    
    // 基类
    class Animal {
    public:
        virtual void makeSound() = 0; // 纯虚函数
    };
    
    // 派生类 Dog
    class Dog : public Animal {
    public:
        void makeSound() override {
            std::cout << "Woof!" << std::endl;
        }
    };
    
    // 派生类 Cat
    class Cat : public Animal {
    public:
        void makeSound() override {
            std::cout << "Meow!" << std::endl;
        }
    };
    
    int main() {
        Dog dog;
        Cat cat;
    
        dog.makeSound(); // 输出 "Woof!"
        cat.makeSound(); // 输出 "Meow!"
    
        return 0;
    }
    

  3. 实现继承:基类可以提供函数的默认实现,派生类可选择重写或使用基类实现。
  4. #include <iostream>
    
    // 基类
    class Animal {
    public:
        virtual void makeSound() {
            std::cout << "Some generic animal sound!" << std::endl;
        }
    };
    
    // 派生类 Dog
    class Dog : public Animal {
    public:
        void makeSound() override {
            std::cout << "Woof!" << std::endl;
        }
    };
    
    // 派生类 Sheep 使用基类的实现
    class Sheep : public Animal {
        // 不重写 makeSound
    };
    
    int main() {
        Dog dog;
        Sheep sheep;
    
        dog.makeSound();  // 输出 "Woof!"
        sheep.makeSound(); // 输出 "Some generic animal sound!"
    
        return 0;
    }
    

  5. 禁止重写:使用 final 关键字可以防止派生类重写基类的实现。
  6. #include <iostream>
    
    // 基类
    class Animal {
    public:
        virtual void makeSound() final {
            std::cout << "Some generic animal sound!" << std::endl;
        }
    };
    
    // 派生类 Dog 不能重写 makeSound
    class Dog : public Animal {
    public:
        // void makeSound() override { // 错误:makeSound 被声明为 final
        //     std::cout << "Woof!" << std::endl;
        // }
    };
    
    int main() {
        Dog dog;
        dog.makeSound(); // 输出 "Some generic animal sound!"
    
        return 0;
    }
    
(3)理解纯虚函数的默认实现

虽然纯虚函数通常在基类中声明但不实现,但在某些情况下,基类也可以为纯虚函数提供一个默认实现。派生类在需要时可以显式调用这个基类的默认实现。这种设计既允许派生类提供自己的实现,又可以在必要时调用基类提供的默认行为。

(4)实现细节
  1. 纯虚函数的声明和默认实现:在基类中声明纯虚函数,并在类外提供其默认实现。
  2. 派生类的实现:派生类可以选择重写纯虚函数,也可以调用基类的默认实现。
  3. 在基类中调用纯虚函数:基类可以在其成员函数中调用纯虚函数的默认实现。
1.基类提供纯虚函数及其默认实现
#include <iostream>

class Animal {
public:
    virtual void makeSound() = 0; // 纯虚函数

    void callBaseSound() {
        Animal::makeSound(); // 调用基类的默认实现
    }
};

// 基类中纯虚函数的默认实现
void Animal::makeSound() {
    std::cout << "Some generic animal sound!" << std::endl;
}
2.派生类实现纯虚函数
class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        std::cout << "Meow!" << std::endl;
    }
};

class Sheep : public Animal {
    // 使用基类的默认实现,不重写 makeSound
};
3.使用多态性调用纯虚函数
int main() {
    Dog dog;
    Cat cat;
    Sheep sheep;

    dog.makeSound();    // 输出 "Woof!"
    cat.makeSound();    // 输出 "Meow!"
    sheep.makeSound();  // 输出 "Some generic animal sound!"

    // 基类中的成员函数调用纯虚函数的默认实现
    sheep.callBaseSound(); // 输出 "Some generic animal sound!"

    return 0;
}

7.静态绑定和动态绑定概述

静态绑定(早绑定)
  • 定义:静态绑定在编译期间确定函数调用或属性访问,绑定的是对象的静态类型(声明的类型)。
  • 特点
    • 编译期确定:函数调用在编译时决定。
    • 非虚函数和缺省参数值:非虚函数和默认参数值在编译期绑定。
    • 基类指针调用基类版本:即使指针指向派生类对象,但调用非虚函数时仍是基类版本。
#include <iostream>

class Base {
public:
    void show() {
        std::cout << "Base::show()" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() {
        std::cout << "Derived::show()" << std::endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出 "Base::show()",因为 show 是静态绑定
    delete b;
    return 0;
}
动态绑定(晚绑定)
  • 定义:动态绑定在运行期间确定函数调用或属性访问(访问对象的成员变量),绑定的是对象的动态类型(实际类型)。
  • 特点
    • 运行时确定:函数调用在运行时决定。
    • 通过虚函数实现:使用 virtual 关键字声明函数实现动态绑定。
    • 支持多态性:虚函数调用根据对象的动态类型,支持多态行为。
#include <iostream>

class Base {
public:
    virtual void show() {
        std::cout << "Base::show()" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived::show()" << std::endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出 "Derived::show()",因为 show 是虚函数,采用动态绑定
    delete b;
    return 0;
}