C++复试笔记(四)

1.重载、重写、重定义(隐藏)的区别

1. 重载(Overloading)

重载指的是在一个类中可以创建多个方法,它们具有相同的名字但参数列表不同(参数的数量、类型或顺序)。返回类型可以相同也可以不同,但仅仅改变返回类型而不改变参数列表不会构成方法重载。

  • 特点:发生在同一个类内部;方法名相同但参数列表不同。

  • 目的:增加函数的灵活性,允许以不同的方式调用同名的方法。

    #include <iostream>

    class Calculator {
    public:
    // 方法重载:两个整数相加
    int add(int a, int b) {
    return a + b;
    }

    复制代码
      // 方法重载:三个整数相加
      int add(int a, int b, int c) {
          return a + b + c;
      }

    };

    int main() {
    Calculator calc;
    std::cout << "Add(5, 3): " << calc.add(5, 3) << std::endl; // 输出: 8
    std::cout << "Add(5, 3, 2): " << calc.add(5, 3, 2) << std::endl; // 输出: 10
    return 0;
    }

2. 重写(Overriding)

重写发生在子类继承父类时,子类重新定义从父类继承来的一个方法,即这个方法在子类中有与父类相同的签名(包括方法名、参数列表和返回类型),并且通常会提供不同的实现。重写的前提是方法必须是可以被继承的(非私有、非最终的)。

  • 特点 :涉及到继承关系;子类中的方法与父类中的方法有完全相同的签名;必须要用虚函数
  • 目的:允许子类根据需要修改或扩展父类的行为。
复制代码
#include <iostream>

class Animal {
public:
    // 使用 virtual 关键字声明虚函数,允许子类重写
    virtual void makeSound() const {
        std::cout << "Some generic animal sound" << std::endl;
    }
};

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

int main() {
    Animal* myDog = new Dog(); // 向上转型
    myDog->makeSound(); // 输出: Bark,调用了子类中重写的方法
    delete myDog;
    return 0;
}

3. 重定义(Hiding)

当子类定义了一个与父类静态方法同名的静态方法时,称作方法的重定义或隐藏。注意,这里讨论的是静态方法的重定义,而不是实例方法的重写。对于静态方法,由于它们属于类本身而不是类的实例,因此不能被重写,只能被隐藏。

  • 特点 :仅适用于静态方法;子类中的静态方法隐藏了父类中的静态方法。

  • 目的:提供一种机制让子类可以用自己版本的静态方法替代父类的静态方法。

    #include <iostream>

    class Parent {
    public:
    static void show() {
    std::cout << "Parent's show method" << std::endl;
    }
    };

    class Child : public Parent {
    public:
    static void show() {
    std::cout << "Child's show method" << std::endl;
    }
    };

    int main() {
    Parent::show(); // 输出: Parent's show method
    Child::show(); // 输出: Child's show method

    复制代码
      // 注意:由于静态方法属于类本身而不是对象,因此不能通过对象指针或引用来调用
      // 下面的代码是错误的,仅用于说明意图
      // Parent* p = new Child();
      // p->show(); // 这行代码会导致编译错误,因为静态方法无法通过对象指针访问
    
      return 0;

    }

2.构造的顺序和析构的顺序

构造函数调用顺序

  1. 基类构造函数:首先调用基类的构造函数。
  2. 成员变量构造函数:接着按它们在类定义中的声明顺序调用成员变量的构造函数。
  3. 派生类构造函数:最后调用派生类的构造函数主体。

析构函数调用顺序

与构造函数相反,析构函数按照以下顺序被调用:

  1. 派生类析构函数:首先调用派生类的析构函数。

  2. 成员变量析构函数:然后按照它们在类定义中声明的逆序调用成员变量的析构函数。

  3. 基类析构函数:最后调用基类的析构函数。

    #include <iostream>

    class Member {
    public:
    Member() {
    std::cout << "Member constructor" << std::endl;
    }
    ~Member() {
    std::cout << "Member destructor" << std::endl;
    }
    };

    class Base {
    public:
    Base() {
    std::cout << "Base constructor" << std::endl;
    }
    ~Base() {
    std::cout << "Base destructor" << std::endl;
    }
    };

    class Derived : public Base {
    Member mem; // 成员变量
    public:
    Derived() {
    std::cout << "Derived constructor" << std::endl;
    }
    ~Derived() {
    std::cout << "Derived destructor" << std::endl;
    }
    };

    int main() {
    {
    Derived d;
    } // 在这个作用域结束时,d会被销毁,触发析构过程

    复制代码
     return 0;

    }

    Base constructor
    Member constructor
    Derived constructor
    Derived destructor
    Member destructor
    Base destructor

3.函数模板和类模板

函数模板

函数模板是通用的函数描述,允许以任意类型的操作数进行操作。函数模板可以用来创建一个函数族,这些函数具有相同算法但作用于不同类型的数据。

函数模板示例
复制代码
#include <iostream>

// 定义一个函数模板,用于交换两个变量的值
template <typename T>
void swapValues(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 1, y = 2;
    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
    swapValues(x, y);
    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;

    double p = 3.5, q = 7.9;
    std::cout << "Before swap: p = " << p << ", q = " << q << std::endl;
    swapValues(p, q);
    std::cout << "After swap: p = " << p << ", q = " << q << std::endl;

    return 0;
}

在这个例子中,swapValues 是一个函数模板,它可以通过不同类型的参数实例化为具体的函数版本。这里我们用它来交换 intdouble 类型的值。

类模板

类模板允许用户为类定义一种模式,而不是一个具体的类型。这使得类中的某些或全部成员可以操作未指定的数据类型。类模板通常用于实现容器类,如链表、栈等,它们需要存储不同类型的数据。

类模板示例
复制代码
#include <iostream>

// 定义一个简单的栈类模板
template <typename T>
class SimpleStack {
private:
    T *stackArray;
    int stackSize;
    int top;
public:
    SimpleStack(int size) : stackSize(size), top(-1) {
        stackArray = new T[stackSize];
    }
    ~SimpleStack() {
        delete[] stackArray;
    }
    bool push(const T &item);
    bool pop(T &item);
    bool isEmpty() const { return top == -1; }
};

template <typename T>
bool SimpleStack<T>::push(const T &item) {
    if (top >= stackSize - 1) {
        return false;
    }
    stackArray[++top] = item;
    return true;
}

template <typename T>
bool SimpleStack<T>::pop(T &item) {
    if (top < 0) {
        return false;
    }
    item = stackArray[top--];
    return true;
}

int main() {
    SimpleStack<int> intStack(10); // 创建一个存储整数的栈
    intStack.push(1);
    intStack.push(2);
    int value;
    if (intStack.pop(value)) {
        std::cout << "Popped from intStack: " << value << std::endl;
    }

    SimpleStack<std::string> stringStack(5); // 创建一个存储字符串的栈
    stringStack.push("Hello");
    stringStack.push("World");
    std::string strValue;
    if (stringStack.pop(strValue)) {
        std::cout << "Popped from stringStack: " << strValue << std::endl;
    }

    return 0;
}

在这个例子中,SimpleStack 是一个类模板,它可以被实例化为存储任何类型数据的栈对象。我们创建了一个 int 类型的栈和一个 std::string 类型的栈,并展示了如何对它们执行入栈和出栈操作。

相关推荐
知识浅谈1 小时前
@Validate 注解的使用-分组案例很有用
java·springboot
Trouvaille ~1 小时前
【Java篇】一法不变,万象归一:方法封装与递归的思想之道
java·开发语言·面向对象·javase·递归·方法·基础入门
Zhava1 小时前
MybatisPlus中的customSqlSegment动态拼接where条件
java·mybatis
Long_poem1 小时前
【自学笔记】Mac OS语言基础知识点总览-持续更新
linux·服务器·笔记
极客先躯1 小时前
高级java每日一道面试题-2025年2月26日-框架篇[Mybatis篇]-Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ?
java·mybatis·嵌套映射·resulttype属性·resultmap属性·results注解·列名别名
Antonio9151 小时前
【设计模式】原型模式
c++·设计模式·原型模式
qq_529835352 小时前
Java实现死锁
java·开发语言·python
牧小七2 小时前
Java --- 根据身份证号计算年龄
java·开发语言
字节源流2 小时前
【SpringMVC】常用注解:@RequestParam
java·开发语言
XMYX-02 小时前
解决 Redis 后台持久化失败的问题:内存不足导致 fork 失败
java·数据库·redis