首页 > 编程语言 >C++ 面向对象、特征、重载和重写、构造和析构、向上和向下转型、深浅拷贝。

C++ 面向对象、特征、重载和重写、构造和析构、向上和向下转型、深浅拷贝。

时间:2024-10-15 09:22:23浏览次数:3  
标签:函数 Person 对象 C++ public 面向对象 和析构 class 构造函数

什么是面向对象(Object-Oriented Programming, OOP)

1.面向对象是一种编程范式,它通过将软件系统的设计和开发分解为“对象”(Object)的方式来实现更好地组织代码。面向对象的核心思想是将程序的结构分为对象,这些对象包含数据和操作这些数据的函数(即方法)。每个对象是类的实例,而类定义了对象的属性和行为。OOP有助于提高代码的可维护性、可重用性和扩展性。

 2. 面向过程和面向对象的区别
面向过程:根据业务逻辑从上到下写代码

面向对象:将数据与函数绑定到一起,进行封装,加快开发程序,减少重复代码的重写过程

面向对象的四大基本特性

  1. 封装(Encapsulation)

    • 定义:封装是将对象的属性和方法包装起来,使得对象内部的数据只能通过定义的方法来访问或修改,从而保护数据的完整性。
    • 目的:隐藏内部实现,防止外部直接访问对象内部数据,提供一个接口与对象交互。

    示例

    class Person {
    private:
        string name;
        int age;

    public:
        // 设置名称
        void setName(string n) { name = n; }
        // 获取名称
        string getName() { return name; }
    };
     

  2. 继承(Inheritance)

    • 定义:继承是从现有类中派生新类的过程,新类可以继承父类的属性和方法,并且可以扩展或修改父类的行为。
    • 目的:复用代码,减少重复,实现代码的层次化结构。

    示例

    #include <iostream>
    using namespace std;

    class Base {
    public:
        int pub_member;
    protected:
        int prot_member;
    private:
        int priv_member;
    };

    class PublicDerived : public Base {
    public:
        void accessBase() {
            pub_member = 10;      // 可以访问
            prot_member = 20;     // 可以访问
            // priv_member = 30;  // 无法访问,编译错误
        }
    };

    class ProtectedDerived : protected Base {
    public:
        void accessBase() {
            pub_member = 10;      // 可以访问,但变为 protected
            prot_member = 20;     // 可以访问
            // priv_member = 30;  // 无法访问,编译错误
        }
    };

    class PrivateDerived : private Base {
    public:
        void accessBase() {
            pub_member = 10;      // 可以访问,但变为 private
            prot_member = 20;     // 可以访问
            // priv_member = 30;  // 无法访问,编译错误
        }
    };

    int main() {
        PublicDerived pubObj;
        pubObj.pub_member = 10;   // 可以访问(public)

        // ProtectedDerived protObj;
        // protObj.pub_member = 10; // 无法访问(protected)

        // PrivateDerived privObj;
        // privObj.pub_member = 10; // 无法访问(private)

        return 0;
    }

    public 继承:
    公开继承 (public) 是最常见的继承方式,表示“is-a”关系,派生类对象可以被当作基类对象使用。
    基类的 public 成员在派生类中仍然保持 public,protected 成员保持 protected。
    基类的 private 成员对派生类不可见。

    protected 继承:
    保护继承 (protected) 限制了派生类对基类成员的访问范围。
    基类的 public 和 protected 成员在派生类中都变为 protected。
    外部无法访问派生类中的这些成员,但派生类的子类仍然可以访问它们。

    private 继承:
    私有继承 (private) 是最为封闭的继承方式,表示“implemented-in-terms-of”关系。
    基类的 public 和 protected 成员在派生类中都变为 private,外部无法访问,派生类的子类也无法访问。
    通常用于当派生类不想暴露基类的接口时。

     
  3. 多态(Polymorphism)

    • 定义:多态允许对象以不同的形式出现,具体表现为同样的方法可以作用于不同的对象,而产生不同的行为。多态分为编译时多态(函数重载、运算符重载)和运行时多态(虚函数)。
    • 目的:通过统一接口处理不同的对象,提高代码的扩展性和灵活性。
    • 也可以理解为用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。实现多态,有二种方式,重写,重载

    示例

    class Animal {
    public:
        virtual void makeSound() {
            cout << "Some generic sound" << endl;
        }
    };

    class Dog : public Animal {
    public:
        void makeSound() override {
            cout << "Bark!" << endl;
        }
    };

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

    void playSound(Animal* animal) {
        animal->makeSound();
    }
     

  4. 抽象(Abstraction)

    • 定义:抽象是指从复杂的现实问题中提取出关键特性,而忽略掉具体的细节。它通过接口或抽象类来定义一组必须被实现的方法。
    • 目的:隐藏复杂实现,仅保留相关功能,从而简化程序结构。

    示例

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

    class Circle : public Shape {
    public:
        void draw() override {
            cout << "Drawing a circle" << endl;
        }
    };

在 C++ 中,重载(Overloading)和重写(Overriding)是两种不同的概念,它们都允许函数的行为在某种程度上发生变化,但在使用方式和适用场景上有显著的不同。

2.1 重载(Overloading)

重载是指在同一个作用域中,允许定义多个同名函数,但它们的参数列表必须不同。重载函数可以根据传递的不同类型或数量的参数执行不同的功能。重载常见于函数重载运算符重载

函数重载的实现

函数重载要求函数名称相同,但参数类型参数个数参数顺序必须不同。

示例

#include <iostream>
using namespace std;

class Calculator {
public:
    // 重载加法函数,处理整数加法
    int add(int a, int b) {
        return a + b;
    }

    // 重载加法函数,处理浮点数加法
    double add(double a, double b) {
        return a + b;
    }

    // 重载加法函数,处理三个数的加法
    int add(int a, int b, int c) {
        return a + b + c;
    }
};

int main() {
    Calculator calc;

    cout << calc.add(10, 20) << endl;        // 调用整数加法
    cout << calc.add(10.5, 20.3) << endl;    // 调用浮点数加法
    cout << calc.add(1, 2, 3) << endl;       // 调用三个整数加法

    return 0;
}
 

运算符重载的实现

运算符重载允许开发者为自定义类型定义特定的操作符行为(如 +-== 等)。

示例

#include <iostream>
using namespace std;

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r, double i) : real(r), imag(i) {}

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

    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3.0, 4.0);
    Complex c2(1.0, 2.0);
    Complex c3 = c1 + c2;  // 使用重载的加法运算符

    c3.display();  // 输出:4 + 6i

    return 0;
}
 

2.2 重写(Overriding)

重写是指在继承关系中,子类重新定义从父类继承而来的函数,以实现不同的行为。重写通常用于运行时多态,通过父类指针或引用调用子类的重写方法。这涉及到虚函数(virtual)的使用。

重写的实现
  • 重写的前提是函数必须在父类中标记为 virtual
  • 子类的重写方法必须和父类的虚函数函数签名完全一致(包括返回类型、参数类型等)。

示例

#include <iostream>
using namespace std;

class Animal {
public:
    // 定义虚函数
    virtual void makeSound() {
        cout << "Some generic animal sound" << endl;
    }
};

class Dog : public Animal {
public:
    // 重写父类的虚函数
    void makeSound() override {
        cout << "Bark!" << endl;
    }
};

class Cat : public Animal {
public:
    // 重写父类的虚函数
    void makeSound() override {
        cout << "Meow!" << endl;
    }
};

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    animal1->makeSound();  // 输出:Bark!
    animal2->makeSound();  // 输出:Meow!

    delete animal1;
    delete animal2;

    return 0;
}
 


在这个例子中,DogCat 都重写了 Animal 类中的 makeSound 方法。当我们通过父类指针调用 makeSound 时,调用的是子类的版本,这就是运行时多态的效果。

2.3 重载与重写的区别

特性重载(Overloading)重写(Overriding)
发生时机编译时:在编译时通过函数签名区分不同的函数。运行时:通过虚函数表在运行时选择合适的重写方法。
适用范围同一个类中,或者在全局作用域中定义的多个同名函数。继承体系中的子类对父类虚函数的重新定义。
函数签名要求必须有不同的参数类型、数量或顺序,返回值可以相同或不同。函数签名必须与父类完全一致,包含返回类型。
使用场景当同一个功能可以通过不同的参数类型或数量实现时。当子类需要修改父类的行为时使用,且通常伴随多态实现。
是否依赖继承关系不依赖继承。依赖继承,必须在子类中定义。
是否需要虚函数不需要。需要虚函数或纯虚函数(virtual 关键字)。
执行效率在编译时就确定调用哪个重载函数,因此效率较高。需要在运行时通过虚函数表查找,效率相对较低。

2.4 总结

  1. 重载是通过改变参数列表来实现相同函数名的不同功能,适用于同一作用域中。
  2. 重写是子类对父类虚函数的重新定义,适用于继承关系,并且通常伴随着多态的实现。
  3. 编译时 vs 运行时:重载是编译时行为,重写是运行时行为。

图表总结

特性重载(Overloading)重写(Overriding)
时机编译时运行时
作用域同一类或全局范围继承关系中的子类和父类
参数要求参数类型、数量或顺序不同与父类方法签名完全一致
虚函数无需虚函数必须是虚函数或纯虚函数
主要功能通过不同参数处理相同逻辑子类修改父类的行为

3.1 构造函数的种类及作用

构造函数是类的一部分,用于在对象创建时初始化对象的状态。C++ 中有以下几种类型的构造函数:

1. 默认构造函数(Default Constructor)

默认构造函数是不带参数或所有参数都有默认值的构造函数,用于在没有显式传递参数时初始化对象。

  • 作用:默认构造函数用于初始化对象为一个默认状态。当没有自定义构造函数时,编译器会自动生成一个默认的构造函数。

示例

class Person {
public:
    Person() {
        cout << "Default constructor called!" << endl;
    }
};

int main() {
    Person p;  // 调用默认构造函数
}
 

2. 带参数的构造函数(Parameterized Constructor)

带参数的构造函数允许在对象创建时传递参数,并根据参数的值初始化对象的属性。

  • 作用:带参数构造函数用于灵活地初始化对象,传递不同的参数以定制化对象的状态。

示例

class Person {
public:
    string name;
    int age;
    
    Person(string n, int a) {  // 带参数构造函数
        name = n;
        age = a;
    }
};

int main() {
    Person p("Alice", 30);  // 创建对象时传递参数
    cout << p.name << ", " << p.age << endl;
}
 

3. 拷贝构造函数(Copy Constructor)

拷贝构造函数用于通过已有对象初始化新对象,即使用一个对象的值来创建另一个相同类型的对象。其形式为 ClassName(const ClassName &other)

  • 作用:在需要创建一个新对象并将已有对象的数据复制给它时使用,常见于对象作为函数参数或返回值的场景。

示例

class Person {
public:
    string name;
    
    // 拷贝构造函数
    Person(const Person &other) {
        name = other.name;
    }
};

int main() {
    Person p1("Alice");
    Person p2 = p1;  // 调用拷贝构造函数
    cout << p2.name << endl;
}
 

4. 移动构造函数(Move Constructor)

移动构造函数用于将资源从一个对象“移动”到另一个对象,避免不必要的复制。其形式为 ClassName(ClassName &&other),传递的是右值引用。

  • 作用:在需要“移动”资源(如堆内存)而不是复制时,可以显著提高性能。

示例

class Person {
public:
    string* name;
    
    Person(string n) {
        name = new string(n);
    }

    // 移动构造函数
    Person(Person&& other) {
        name = other.name;
        other.name = nullptr;  // 释放原对象的资源
    }

    ~Person() {
        delete name;
    }
};

int main() {
    Person p1("Alice");
    Person p2 = std::move(p1);  // 调用移动构造函数
}
 

3.2 析构函数(Destructor)

析构函数在对象的生命周期结束时被调用,用于释放资源。析构函数的名称与类名相同,但前面有个波浪符号 ~,且不接受参数。

作用
  • 用于清理对象在生存期内分配的资源(如内存、文件句柄、网络连接等),防止资源泄露。

示例

class Person {
public:
    Person() {
        cout << "Constructor called!" << endl;
    }

    ~Person() {
        cout << "Destructor called!" << endl;
    }
};

int main() {
    Person p;  // 构造函数会在对象创建时调用,析构函数会在对象销毁时调用
}
 

3.3 只定义析构函数时,自动生成哪些构造函数?

如果在类中只定义析构函数而没有定义构造函数,编译器将自动生成以下几种构造函数:

  1. 默认构造函数:如果类中没有任何构造函数,编译器将生成一个默认构造函数,用于默认初始化对象。
  2. 拷贝构造函数:如果类没有自定义的拷贝构造函数,编译器将生成一个浅拷贝的拷贝构造函数。
  3. 拷贝赋值运算符:如果没有自定义的赋值运算符,编译器会生成一个默认的赋值运算符进行浅拷贝。
  4. 移动构造函数(如果需要):编译器可能会根据需要生成一个默认的移动构造函数。
  5. 移动赋值运算符(如果需要):类似于移动构造函数。

注意:如果类中有指针或其他需要深拷贝的资源管理,默认生成的构造函数和赋值运算符可能会导致资源管理问题(如重复释放内存)。

3.4 一个类默认会生成哪些函数?

当你定义一个类但没有显式定义构造函数、析构函数或赋值运算符时,编译器会自动生成以下函数:

  1. 默认构造函数:用于在不传递参数时初始化对象。如果类没有定义任何构造函数,编译器会生成一个默认构造函数。

  2. 拷贝构造函数:用于通过另一个同类型的对象初始化新对象。默认的拷贝构造函数执行浅拷贝,将对象的所有成员逐字复制。

  3. 拷贝赋值运算符(operator=):用于将一个对象赋值给另一个相同类型的对象。编译器生成的默认赋值运算符同样执行浅拷贝。

  4. 析构函数:用于销毁对象并释放资源。默认析构函数不会执行任何特定操作,只会清理对象的内存。

  5. 移动构造函数:如果类使用了动态资源管理(如指针),并且你未定义移动构造函数,编译器可能会自动生成一个移动构造函数。

  6. 移动赋值运算符:同样地,如果类涉及动态资源,编译器可能会生成一个移动赋值运算符。

3.5 总结

函数类型自动生成的条件作用
默认构造函数如果没有任何构造函数初始化对象,所有成员初始化为默认值
拷贝构造函数如果没有自定义拷贝构造函数使用已有对象初始化新对象,浅拷贝
拷贝赋值运算符如果没有自定义赋值运算符将一个对象赋值给另一个对象,浅拷贝
析构函数如果没有自定义析构函数在对象生命周期结束时调用,释放资源
移动构造函数如果没有自定义移动构造函数,且需要时生成将资源从一个对象“移动”到另一个对象,避免不必要的复制
移动赋值运算符如果没有自定义移动赋值运算符,且需要时生成将资源从一个对象移动给另一个对象

4.1 C++ 类对象的初始化顺序

在 C++ 中,类对象的初始化顺序是确定的,并且遵循以下规则:

  1. 基类先于派生类

    • 如果类有继承关系,则先初始化基类的部分,再初始化派生类的部分。
    • 初始化顺序是基类先于派生类
  2. 成员变量按照声明顺序初始化

    • 类的成员变量按照它们在类中的声明顺序进行初始化,而不是按照它们在初始化列表中的顺序。
  3. 初始化列表优先于构造函数体

    • 如果类的构造函数包含初始化列表,成员变量在进入构造函数体之前就会被初始化。

示例

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base class initialized" << endl;
    }
};

class Derived : public Base {
public:
    int x;
    int y;

    // 使用初始化列表
    Derived(int a, int b) : x(a), y(b) {
        cout << "Derived class initialized" << endl;
    }
};

int main() {
    Derived d(10, 20);
    return 0;
}

输出顺序

Base class initialized
Derived class initialized

这里的顺序是:

  • 基类 Base 先被初始化。
  • 派生类 Derived 中的成员 xy 按照声明顺序初始化。
  • 构造函数体最后执行。

4.2 多重继承下的初始化顺序

在多重继承中,基类的初始化顺序与它们在类声明中的顺序一致,而不是在初始化列表中的顺序。

示例

#include <iostream>
using namespace std;

class Base1 {
public:
    Base1() {
        cout << "Base1 class initialized" << endl;
    }
};

class Base2 {
public:
    Base2() {
        cout << "Base2 class initialized" << endl;
    }
};

class Derived : public Base1, public Base2 {
public:
    Derived() {
        cout << "Derived class initialized" << endl;
    }
};

int main() {
    Derived d;
    return 0;
}
 

输出顺序

Base1 class initialized
Base2 class initialized
Derived class initialized
 

在多重继承的情况下,Base1Base2 的初始化顺序与它们在 Derived 类中的声明顺序一致(即从左到右),而派生类 Derived 最后被初始化。

4.3 类型转换:上向下转型和向下转型

4.3.1 上向转型(Upcasting)

上向转型是指将派生类对象的指针或引用转换为基类类型。这种转换是安全的,因为派生类对象包含基类的部分。

  • 特点
    • 隐式转换:编译器自动进行上向转型,无需显式指定。
    • 转换后,只有基类的成员可以通过该指针或引用访问。

示例

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

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

int main() {
    Derived d;
    Base* basePtr = &d;  // 上向转型
    basePtr->show();     // 调用基类的 show() 方法
}

输出

Base class

在上向转型时,即使派生类重写了基类的方法,使用基类指针调用的仍然是基类的方法,除非基类的方法是虚函数virtual)。

4.3.2 下向转型(Downcasting)

下向转型是指将基类对象的指针或引用转换为派生类类型。这种转换不安全,因为基类对象可能并不包含派生类的部分,因此需要使用 dynamic_cast 来确保转换的安全性。

  • 特点
    • 下向转型需要显式进行。
    • 使用 dynamic_cast 来确保安全性,失败时返回 nullptr
    • 通常涉及多态和虚函数。

示例

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

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

int main() {
    Base* basePtr = new Derived();  // 上向转型
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);  // 下向转型
    
    if (derivedPtr) {
        derivedPtr->show();  // 输出:Derived class
    } else {
        cout << "Failed to cast" << endl;
    }
    
    delete basePtr;
}
 

在这个例子中,dynamic_cast 确保下向转型的安全性,如果转换失败,derivedPtr 会是 nullptr

4.4 深拷贝和浅拷贝

4.4.1 浅拷贝(Shallow Copy)

浅拷贝只复制对象的,对于指针成员,它只复制指针本身的地址,而不复制指针所指向的对象。这可能导致多个对象指向同一个内存区域,进而引发资源共享问题或重复释放的问题。

示例

class Person {
public:
    char* name;

    Person(const char* n) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    // 浅拷贝构造函数(默认)
    Person(const Person& other) {
        name = other.name;  // 只复制指针地址,未分配新内存
    }

    ~Person() {
        delete[] name;  // 重复释放可能会导致错误
    }
};
 

问题:当一个对象被销毁时,指针所指向的内存会被释放,如果有多个对象共享相同的指针地址,会导致重复释放内存的错误。

4.4.2 深拷贝(Deep Copy)

深拷贝不仅复制对象本身的值,还为每个指针成员分配新的内存,并复制指针指向的内容。这样每个对象都有独立的资源,不会共享同一个内存区域。

实现深拷贝的拷贝构造函数
class Person {
public:
    char* name;

    Person(const char* n) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    // 深拷贝构造函数
    Person(const Person& other) {
        name = new char[strlen(other.name) + 1];  // 分配新内存
        strcpy(name, other.name);  // 复制内容
    }

    ~Person() {
        delete[] name;  // 正常释放内存
    }
};

int main() {
    Person p1("Alice");
    Person p2 = p1;  // 调用深拷贝构造函数

    return 0;
}

深拷贝的优点

  • 每个对象都拥有独立的内存资源,不会互相影响。
  • 避免了共享指针带来的资源管理问题,如重复释放。

4.5 总结

概念描述
初始化顺序基类先于派生类,成员按照声明顺序初始化,初始化列表先于构造函数体。
多重继承初始化顺序基类按声明顺序初始化,派生类最后初始化。
上向转型将派生类对象转换为基类类型,隐式转换,常用于多态。
下向转型将基类对象转换为派生类类型,使用 dynamic_cast 进行安全转换。
浅拷贝只复制指针地址,多个对象共享同一块内存,可能导致资源管理问题。
深拷贝为每个指针成员分配新的内存并复制内容,确保每个对象有独立的资源,避免共享指针问题。

标签:函数,Person,对象,C++,public,面向对象,和析构,class,构造函数
From: https://blog.csdn.net/qq_50373827/article/details/142909168

相关文章

  • GESP2024年6月认证C++四级( 第三部分编程题(1))
    参考程序代码:#include<bits/stdc++.h>usingnamespacestd;constintN=55;intw[N][N];intn,m;boolcheck(intxa,intya,intxb,intyb){ inta[2]={0,0}; for(inti=xa;i<=xb;i++) { for(intj=ya;j<=yb;j++) { a[w[1][j]]++; } } returna[0......
  • C++模板初阶,只需稍微学习;直接起飞;泛型编程
    ......
  • C++中的静态函数
    静态函数(static function)是一种特殊类型的函数,它可以存在于类或者在文件级别,具有不同的作用和特性。静态函数通过 static 关键字进行声明,具体分为 类中的静态成员函数 和 文件级别的静态函数,它们在不同的上下文中发挥不同的作用。1. 类中的静态成员函数在类中,静态成员函......
  • c++中的函数重载
    C++中的 函数重载(FunctionOverloading)是一种多态性特性,允许在同一作用域内定义 多个同名函数,只要这些函数的参数列表不同(参数的类型、数量或顺序不同)。编译器根据函数调用时传递的参数类型和数量来决定调用哪个函数。1. 函数重载的规则C++编译器通过以下规则来区分重载的......
  • 实验1 现代C++编程初体验
    实验任务1:task1.cpp点击查看代码//现代C++标准库、算法库体验//本例用到以下内容://1.字符串string,动态数组容器类vector、迭代器//2.算法库:反转元素次序、旋转元素//3.函数模板、const引用作为形参#include<iostream>#include<string>#include<vector>......
  • C++中int main(int argc, char* argv[])形参解释
    在 intmain(intargc,char*argv[]) 中,argc 和 argv 是主函数 main 的参数,用于处理命令行输入参数。它们允许程序从命令行接收额外的输入值,常用于控制程序的执行行为或传递信息。参数解释argc(argumentcount):表示从命令行传递给程序的参数个数,包括程序本身的名字。它......
  • 《C++内存对齐策略:提升性能的关键之路》
    在C++编程的广阔世界中,高效的内存对齐策略是一个至关重要却常常被忽视的主题。它不仅影响着程序的性能,还关系到内存的使用效率和稳定性。今天,我们就来深入探讨一下如何在C++中实现高效的内存对齐策略。一、为什么内存对齐如此重要?内存对齐在C++中具有重大意义。首先,它......
  • 《C++与区块链节点:职场新势力的崛起》
    在当今数字化的职场环境中,区块链技术正以其独特的魅力和巨大的潜力吸引着众多专业人士的目光。而作为一种强大的编程语言,C++在区块链节点的实现中发挥着至关重要的作用。今天,我们就来探讨一下在职场中,如何使用C++进行区块链节点的实现。一、区块链技术的魅力与挑战区块链......
  • [C++] 红黑树的实现:原理与底层解析
    文章目录@[toc]红黑树的概念红黑树的规则红黑树如何确保最长路径不超过最短路径的2倍红黑树规则最短路径与最长路径的分析最短路径:全黑路径最长路径:红黑交替路径结论:红黑树的平衡性如何保障操作效率红黑树的实现红黑树的节点结构红黑树的插入操作插入基本步骤插入......
  • 实验1 现代C++编程初体验
    任务1:源代码task1.cpp1#include<iostream>2#include<string>3#include<vector>4#include<algorithm>56usingnamespacestd;78template<typenameT>9voidoutput(constT&c);1011voidtest1();12voidtes......