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

这篇具有很好参考价值的文章主要介绍了C++基础(一) —— 面向对象(1)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


概念

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

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

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

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

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

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

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

一、类和对象、this指针

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

二、构造函数和析构函数

gouzaohanshu.cpp

#include <iostream>
#include <string>

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 <iostream>

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 <bits/stdc++.h>

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 <bits/stdc++.h>

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文章来源地址https://www.toymoban.com/news/detail-468112.html

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

#include<iostream>
#include<cstring>

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++基础(一) —— 面向对象(1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java基础教程】(七)面向对象篇 · 第一讲:上干货!面向对象的特性、类与对象、内存结构引用分析、垃圾收集器 GC处理、封装性详解、构造方法、匿名对象、简单 Java 类~

    程序是将数据和逻辑封装在一起的代码段。在Java中,方法是常用的代码段封装方式。然而,在Java中,方法必须存在于一个类中才能使用。因此,我们将进入本章的核心内容——面向对象编程。 利用面向对象设计的程序可以实现代码的重用,并方便开发者进行项目维护。面向

    2024年02月13日
    浏览(40)
  • C++基础(一) —— 面向对象(2)

    子类可以访问基类的public和protected 基类的private只有自己和友元才能访问(友元会破坏类的封装性) 除构造函数和析构函数 ,派生类从基类可以继承来所有的成员(变量和方法) 派生类通过 调用 基类的构造函数来初始化从基类继承来的成员变量 派生类对象构造和析构的过程:

    2024年02月07日
    浏览(34)
  • C++基础(一) —— 面向对象(1)

    面向对象四大特性: 抽象 :抽象是一种将对象的共同特征提取出来并定义成一个通用模板的过程。类的抽象是指将一个类的共同属性和行为抽象出来,定义一个通用的类模板,而不关注具体的实现细节。 封装性 :数据和代码捆绑在一起,避免外界干扰和不确定性访问。封装

    2024年02月07日
    浏览(27)
  • C++ 递归与面向对象编程基础

    递归是一种使函数调用自身的技术。这种技术提供了一种将复杂问题分解为简单问题的方法,从而更容易解决问题。 递归可能有点难以理解。理解其工作原理的最佳方法是通过实验来尝试。 将两个数字相加很容易做到,但将一系列数字相加就更复杂了。在下面的示例中,通

    2024年04月16日
    浏览(37)
  • 【C++干货基地】面向对象核心概念 const成员函数 | 初始化列表 | explicit关键字 | 取地址重载

    🎬 鸽芷咕 :个人主页  🔥 个人专栏 : 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活!   哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作

    2024年04月23日
    浏览(46)
  • 【C++庖丁解牛】面向对象的三大特性之一多态 | 抽象类 | 多态的原理 | 单继承和多继承关系中的虚函数表

    🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 需要声明的,本节课件中的代码及解释都是在vs2013下的x86程序中,涉及的指针都是4bytes。如果要其他平台

    2024年04月10日
    浏览(56)
  • C++面向对象程序设计-基础入门(超详细)

    目录 一、c++概述 二、初识c++ 1、第一个c++程序  2、c++面向对象的三大特性(重要) 三、作用域运算符:: 1、使用namespace创建一个命名空间 2、命名空间只能定义在全局 3、 命名空间嵌套  4、随时将新的成员加入命名空间 5、命名空间中 函数的声明和实现分开   6、

    2024年02月16日
    浏览(50)
  • C++ 基础知识 五 ( 来看来看 面向对象的继承 上篇 )

    C++ 继承是指派生类(子类)从基类(父类)继承属性和行为的过程。我们可以创建一个新的类,该类可以继承另一个类的数据属性和方法。 在上述代码中,我们定义了一个父类 Person 与一个子类 Student。Student 类继承了 Person 类的属性和方法,包括 name、age、gender 和 eat() 函数

    2024年02月03日
    浏览(96)
  • 15面向对象特性

    在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到,其含义是其他程序无法调用。要了解封装,离不开“私有化”,就是将类或者是函数中的某些属性限制在某个区域之内,外部无法调用。 封装的作用: 1、保护隐私(把

    2023年04月23日
    浏览(36)
  • 【面向对象语言三大特性之 “继承”】

    目录 1.继承的概念及定义 1.1继承的概念 1.2 继承定义 1.2.1定义格式  1.2.2继承关系和访问限定符  1.2.3继承基类成员访问方式的变化 2.基类和派生类对象赋值转换 3.继承中的作用域 4.派生类的默认成员函数 5.继承与友元 6. 继承与静态成员 7.复杂的菱形继承及菱形虚拟继承 8.继

    2023年04月08日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包