C++基础(一) —— 面向对象(1)

文章目录

  • 概念
  • 一、类和对象、this指针
  • 二、构造函数和析构函数
    • 2.1 拷贝构造函数(深拷贝浅拷贝)
      • 2.1.1 顺序栈
      • 2.1.2 循环队列
      • 2.1.3 实现string
    • 2.2 移动构造函数


概念

面向对象四大特性:
抽象:抽象是一种将对象的共同特征提取出来并定义成一个通用模板的过程。类的抽象是指将一个类的共同属性和行为抽象出来,定义一个通用的类模板,而不关注具体的实现细节。

封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。封装可以使得代码模块化。
优点:
确保用户代码不会无意间破坏封装对象的状态;
被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。

继承性:让某种类型对象获得另一个类型对象的属性和方法,继承可以扩展已存在的代码

多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态),多态的目的则是为了接口重用。

多态
​多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
​C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。通过多态性,我们可以使用指向派生类的基类指针来调用相同的函数,根据对象的实际类型,动态地选择正确的实现。这种灵活性使得我们可以以统一的方式处理不同的对象,而无需关注它们的具体类型。

​多态可分为静态多态和动态多态
静态多态是指在编译期间就可以确定函数的调用地址,并生产代码,这就是静态的,也就是说地址是早早绑定的,静态多态也往往被叫做静态联编。 静态多态往往通过函数重载(运算符重载)和模版(泛型编程)来实现。
动态多态则是指函数调用的地址不能在编译器期间确定,必须需要在运行时才确定,这就属于晚绑定,动态多态也往往被叫做动态联编。 ​

动态绑定
​当使用基类的引用或指针调用虚成员函数时会执行动态绑定。动态绑定直到运行的时候才知道到底调用哪个版本的虚函数,所以必为每一个虚函数都提供定义,而不管它是否被用到,这是因为连编译器都无法确定到底会使用哪个虚函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个。

一、类和对象、this指针

this指针是一个特殊的指针,它指向当前对象的地址。可以通过this指针在类的成员函数中访问当前对象的成员变量和成员函数。
this指针通常用于可能存在歧义或使代码更明确的情况,尤其是当存在与成员变量同名的参数或局部变量时。

二、构造函数和析构函数

gouzaohanshu.cpp

#include 
#include 

class MyClass {
private:
    int value;
    std::string name;

public:
    // 默认构造函数
    MyClass() : value(0), name("") {
        std::cout << "Default constructor called." << std::endl;
    }

    // 参数化构造函数
    MyClass(int val, const std::string& n) : value(val), name(n) {
        std::cout << "Parameterized constructor called." << std::endl;
    }

    // 拷贝构造函数
    MyClass(const MyClass& other) : value(other.value), name(other.name) {
        std::cout << "Copy constructor called." << std::endl;
    }

    // 移动构造函数
    MyClass(MyClass&& other) noexcept : value(std::move(other.value)), name(std::move(other.name)) {
        std::cout << "Move constructor called." << std::endl;
    }

    void display() const {
        std::cout << "Value: " << value << ", Name: " << name << std::endl;
    }
};

int main() {
    MyClass obj1;                      // 调用默认构造函数
    obj1.display();                    // 输出: Value: 0, Name: 

    MyClass obj2(10, "Object 2");       // 调用参数化构造函数
    obj2.display();                    // 输出: Value: 10, Name: Object 2

    MyClass obj3 = obj2;                // 调用拷贝构造函数
    obj3.display();                    // 输出: Value: 10, Name: Object 2

    MyClass obj4 = std::move(obj3);     // 调用移动构造函数
    obj4.display();                    // 输出: Value: 10, Name: Object 2

    return 0;
}

2.1 拷贝构造函数(深拷贝浅拷贝)

概念:
浅拷贝(Shallow Copy)只复制了对象的引用而没有复制实际的数据。因此,当对原对象进行修改时,新对象也会受到影响,两个对象会指向同一块内存地址

深拷贝(Deep Copy)是指创建一个新对象,新对象的内容是原对象的完全复制,包括数据和引用的对象。深拷贝会递归地复制对象的所有引用,因此原对象和新对象是完全独立的,互不影响,两个对象拥有各自独立的内存地址

实现:
浅拷贝在 C++ 中可以通过复制构造函数或赋值运算符来实现。默认情况下,C++ 类会使用编译器提供的浅拷贝机制,它仅复制对象的成员变量。这意味着对象中的指针成员变量将被复制为相同的指针值,而不会创建新的独立对象。

深拷贝需要手动编写拷贝构造函数或赋值运算符重载函数来复制对象的数据并处理指针成员变量的拷贝。你可以使用动态内存分配函数(如 new 和 delete)来创建新的独立对象。

拷贝构造函数为什么要传引用:
拷贝构造函数通常应该接受一个常量引用作为参数,以确保在拷贝过程中不会修改原始对象的状态。这符合了只读不修改原始对象的设计原则。拷贝构造函数的声明如下。
ClassName(const ClassName& other);
引用的好处如下:
1 避免无限递归(重点):
如果拷贝构造函数的参数是按值传递而不是引用,那么在调用拷贝构造函数时,会创建一个新的对象作为参数,这会导致另一个拷贝构造函数被调用,然后又创建一个新的对象作为参数,如此循环下去,形成无限递归。通过传递引用作为参数,可以避免这种无限递归的问题。
2 避免不必要的复制:
如果拷贝构造函数的参数是按值传递,那么在调用拷贝构造函数时,会复制传入的对象的所有成员到新创建的对象中。这涉及到了额外的内存分配和数据复制的开销。而通过传递引用,只需要传递对象的引用而不是整个对象,可以避免不必要的复制操作,提高效率。

deep-shallow-copy.cpp

#include 

class MyClass {
private:
    int* data;

public:
    // 构造函数
    MyClass(int value) {
        data = new int(value);
    }

    // 拷贝构造函数(浅拷贝)
    MyClass(const MyClass& other) {
        data = other.data;  // 仅复制指针值,而不是创建新对象
    }

    // 赋值运算符重载函数(浅拷贝)
    MyClass& operator=(const MyClass& other) {
        if (this == &other) {
            return *this;
        }
        data = other.data;  // 仅复制指针值,而不是创建新对象
        return *this;
    }

    // 拷贝构造函数(深拷贝)
    MyClass(const MyClass& other) {
        data = new int(*other.data);  // 创建新的独立对象
    }

    // 赋值运算符重载函数(深拷贝)
    MyClass& operator=(const MyClass& other) {
        if (this == &other) {
            return *this;
        }
        delete data;                // 释放原有对象的内存
        data = new int(*other.data); // 创建新的独立对象
        return *this;
    }

    // 析构函数
    ~MyClass() {
        delete data;  // 释放内存
    }

    // 示例函数
    void printData() {
        std::cout << "Data: " << *data << std::endl;
    }
};

int main() {
    MyClass obj1(5);
    MyClass obj2 = obj1;   // 浅拷贝
    MyClass obj3(obj1);    // 深拷贝

    obj1.printData();      // 输出: Data: 5
    obj2.printData();      // 输出: Data: 5
    obj3.printData();      // 输出: Data: 5

    // 修改原对象的数据
    *obj1.data = 10;

    obj1.printData();      // 输出: Data: 10
    obj2.printData();      // 输出: Data: 10(浅拷贝,受影响)
    obj3.printData();      // 输出: Data: 5(深拷贝,不受影响)

    return 0;
}

2.1.1 顺序栈

深拷贝顺序栈
tuo_oopstack_deepcopy.cpp

#include 

using namespace std;

class SeqStack
{
public:
    // 构造函数 
    SeqStack(int size = 10)
    {
        cout << " SeqStack() Ptr" << this << endl;
        _pstack = new int[size];
        _top = -1;
        _size = size;
    }

    // 自定义的拷贝构造函数 《= 对象的浅拷贝现在有问题了
    SeqStack(const SeqStack &src)
    {
        cout << "SeqStack(const SeqStack &src)" << endl;
        _pstack = new int[src._size];
        for (int i = 0; i <= src._top; ++i)
        {
            _pstack[i] = src._pstack[i];
        }
        _top = src._top;
        _size = src._size;
    }

    // 析构函数
    ~SeqStack() 
    {
        cout << this << " ~SeqStack()" << endl;
        delete[]_pstack;
        _pstack = nullptr;
    }

    // 赋值重载函数 s1 = s1;
    void operator=(const SeqStack &src)
    {
        cout << "operator=" << endl;
        // 防止自赋值
        if (this == &src)
            return;

        // 需要先释放当前对象占用的外部资源
        delete[]_pstack;

        _pstack = new int[src._size];
        for (int i = 0; i <= src._top; ++i)
        {
            _pstack[i] = src._pstack[i];
        }
        _top = src._top;
        _size = src._size;
    }

    void push(int val)
    {
        if (full()) resize();
        _pstack[++_top] = val;
    }
    void pop()
    {
        if (empty()) return;
        --_top;
    }
    int top()
    {
        return _pstack[_top];
    }
    bool empty() { return _top == -1; }
    bool full() { return _top == _size - 1; }

private:

    int *_pstack; // 动态开辟数组,存储顺序栈的元素
    int _top; // 指向栈顶元素的位置
    int _size; // 数组扩容的总大小

    void resize()
    {
        int *ptmp = new int[_size * 2];
        for (int i = 0; i < _size; ++i)
        {
            ptmp[i] = _pstack[i];
        } // memcpy(ptmp, _pstack, sizeof(int)*_size); realloc
        delete[]_pstack;
        _pstack = ptmp;
        _size *= 2;
    }
};

int main()
{
    SeqStack s; // 没有提供任何构造函数的时候,会为你生成默认构造和默认析构,是空函数
    SeqStack s1(10);
    SeqStack s2 = s1; // #1  默认拷贝构造函数-》做直接内存数据拷贝
    SeqStack s3(s1); // #2  

    // s2.operator=(s1) 
    // void operator=(const SeqStack &src)
    s1 = s1; // 默认的赋值函数 =》 做直接的内存拷贝 

    return 0;
}

2.1.2 循环队列

class Queue
{
public:
	Queue(int size = 5)
	{
		_pQue = new int[size];
		_front = _rear = 0;
		_size = size;
	}
	//Queue(const Queue&) = delete;
	//Queue& operator=(const Queue&) = delete;
	Queue(const Queue &src)
	{
		_size = src._size;
		_front = src._front;
		_rear = src._rear;
		_pQue = new int[_size];
		for (int i = _front; 
			i != _rear; 
			i = (i + 1) % _size)
		{
			_pQue[i] = src._pQue[i];
		}
	}
	Queue& operator=(const Queue &src)
	{
		if (this == &src)
			return *this;

		delete[]_pQue;

		_size = src._size;
		_front = src._front;
		_rear = src._rear;
		_pQue = new int[_size];
		for (int i = _front;
			i != _rear;
			i = (i + 1) % _size)
		{
			_pQue[i] = src._pQue[i];
		}
		return *this;
	}
	~Queue()
	{
		delete[]_pQue;
		_pQue = nullptr;
	}
	void push(int val) // 入队操作
	{
		if (full())
			resize();
		_pQue[_rear] = val;
		_rear = (_rear + 1) % _size;
	}
	void pop() // 出队操作
	{
		if (empty())
			return;
		_front = (_front + 1) % _size;
	}
	int front() // 获取队头元素
	{
		return _pQue[_front];
	}
	bool full() { return (_rear + 1) % _size == _front; }
	bool empty() { return _front == _rear; }
private: 
	int *_pQue; // 申请队列的数组空间
	int _front; // 指示队头的位置
	int _rear;  // 指示队尾的位置
	int _size;  // 队列扩容的总大小

	void resize()
	{
		int *ptmp = new int[2 * _size];
		int index = 0;
		for (int i = _front;
			i != _rear;
			i = (i + 1) % _size)
		{
			ptmp[index++] = _pQue[i];
		}
		delete[]_pQue;
		_pQue = ptmp;
		_front = 0;
		_rear = index;
		_size *= 2;
	}
};
int main()
{
	Queue queue;
	for (int i = 0; i < 20; ++i)
	{
		queue.push(rand() % 100);
	}

	while (!queue.empty())
	{
		cout << queue.front() << " ";
		queue.pop();
	}
	cout << endl;

	Queue queue1 = queue;
	queue1 = queue;

	return 0;
}

2.1.3 实现string

深拷贝string

ez_Mystring_oop.cpp

#include 

using namespace std;

class String
{
public:
    String(const char *str = nullptr) // 普通构造函数
    {
        if (str != nullptr)
        {
            m_data = new char[strlen(str) + 1];
            strcpy(this->m_data, str);
        }
        else
        {
            m_data = new char[1]; // new char;
            *m_data = '\0'; // 0
        }
    }
    String(const String &other)  // 拷贝构造函数
    {
        m_data = new char[strlen(other.m_data) + 1]; // 深拷贝
        strcpy(m_data, other.m_data);
    }
    ~String(void)  // 析构函数
    {
        delete[]m_data;
        m_data = nullptr;
    }
    // String& 是为了支持连续的operator=赋值操作
    String& operator=(const String &other) // 赋值重载函数
    {
        if (this == &other)
        {
            return *this;  // str1
        }

        delete[]m_data;    // 析构

        m_data = new char[strlen(other.m_data) + 1];
        strcpy(m_data, other.m_data);
        return *this; // str1
    }
private:
    char *m_data; // 用于保存字符串
};
int main()
{
    // 调用带const char*参数的构造函数
    String str1;
    String str2("hello");
    String str3 = "world";

    // 调用拷贝构造函数
    String str4 = str3;
    String str5(str3);

    // 调用赋值重载函数
    /*
    str1 = str2
    str1.operator=(str2) => str1
    str3 = str1
    */
    str3 = str1 = str2;

    return 0;
}

2.2 移动构造函数

移动构造函数实现对象的移动操作。
声明:
ClassName(ClassName&& other);
&&:右值引用(R-value reference)
移动构造函数使用右值引用作为参数,接收一个将要移动的对象。通过移动构造函数,可以有效地将资源(如指针、动态分配的内存等)从一个对象转移到另一个对象,从而不需要进行深拷贝。通常,移动构造函数会将要移动对象的成员指针复制到新对象中,并将原对象的成员指针设置为nullptr,以确保移动后的对象拥有资源的所有权。

移动构造函数通常用于提高性能,避免不必要的资源复制和内存分配。它在C++11标准引入的右值引用概念后得以实现。当使用移动语义进行对象的移动时,编译器会优先选择移动构造函数而不是拷贝构造函数,从而提高代码的执行效率。

需要注意的是,如果类定义了移动构造函数但没有拷贝构造函数,那么编译器会生成默认的拷贝构造函数,执行的是浅拷贝操作。如果类定义了拷贝构造函数但没有移动构造函数,编译器会自动生成默认的移动构造函数,执行的是与拷贝构造函数类似的深拷贝操作。

完整的手写string

/*
1.实现string的输入输出
2.实现默认构造,拷贝构造,移动构造
3.实现拷贝赋值, 移动赋值 
*/

#include
#include

using namespace std;

class MyString {
public:
    // 构造函数
    MyString(const char* str = nullptr) {
        if (str == nullptr) {
            s = new char[1];
            strcpy(s, "");
        } else {
            s = new char[strlen(str) + 1];
            strcpy(s, str);
        }
    }

    // 析构函数
    ~MyString(void) {
        if (s != nullptr) {
            delete []s;
            s = nullptr;
        }
    }

    //拷贝构造
    MyString(const MyString &str) {
        s = new char[strlen(str.s) + 1];
        strcpy(s, str.s);
    }

    //移动构造
    MyString(MyString &&str) {
        s = str.s;
        str.s = nullptr;
    }

    //拷贝赋值
    MyString& operator=(const MyString &str) {
        if (this != &str) {
            MyString tmp(str);
            swap(tmp.s, s);
        }
        return *this;
    }

    // 移动赋值
    MyString& operator= (MyString&& str) {
        if (this != &str) {
            delete[] s;
            s = str.s;
            str.s = nullptr;
        }
        return *this;
    }

    size_t length(void) const {
        return strlen(s);
    }

    const char *c_str(void) const {
        return s;
    }

    // 重载[]运算符
    char &operator[](size_t index) {
        return s[index];
    }

    const char &operator[](size_t index) const{
        return s[index];
    }

    friend ostream& operator<< (ostream &os, const MyString &s);
    friend istream& operator>> (istream &is, MyString &s);
private:
    char* s;
};

ostream& operator<< (ostream &os, const MyString &s) {
    os << s.s;
    return os;
}

istream& operator>> (istream &is, MyString &s) {
    is >> s.s;
    return is;
}

int main() {
    MyString s1("Hello World!!!");
    cout << s1 << endl;

    MyString s;
    cin >> s;
    cout << s << endl;

    MyString s2(s1);
    cout << s2 << endl;


    system("pause");
    return 0;
}

你可能感兴趣的:(c++,开发语言)