IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++面向对象--类和对象哪些你不知道的细节原理 -> 正文阅读

[C++知识库]C++面向对象--类和对象哪些你不知道的细节原理

一、类和对象、this指针

1、谈谈对面向对象(OO,Object Oriented)的理解

类(属性和行为) =》 实体的抽象类型(ADT)
面向对象的思维是,当我碰到这个问题域的时候,碰到这个程序的时候,我首先应该把这个问题里有哪些对象,对象与对象之间有什么关系抽象出来。对象通过“属性(attribute)”和“方法(method)”来分别对应事物所具有的静态属性和动态属性。类是用于描述同一类的对象的一个抽象的概念,类中定义了这一类对象所具有的静态属性和动态属性。类可以看成一类对象的模板,对象可以看成该类的一个具体实例。OOP有四大特征:抽象、封装、继承和多态。封装就是访问限定符:public公有的、private私有的、protected保护的。

#include <iostream>
using namespace std;

//类   =》  商品
const int NAME_LEN = 20;
class CGoods//=>商品的抽象数据类型
{
public://给外部提供公有的成员方法,来访问私有的属性
    //做商品数据的初始化用的
    void init(const char* name, double price, int amount);
    //打印商品信息
    void show();

    //给成员变量提供一个getXXX或setXXX的方法 ,类体内实现的方法,自动处理成inline内联函数
    void setName(char* name) { strcpy(_name, name); }
    void setPrice(double price) { _price = price; }
    void setAmount(int amount) { _amount = amount; }

    const char* getName() { return _name; }
    double getPrice() { return _price; }
    int getAmount() { return _amount; }

private://属性一般都是私有的成员变量
    char _name[NAME_LEN];
    double _price;
    int _amount;
};

void CGoods::init(const char* name, double price, int amount)
{
    strcpy(_name, name);
    _price = price;
    _amount = amount;
}
void CGoods::show()
{
    cout << "name:" << _name << endl;
    cout << "price:" << _price << endl;
    cout << "amount" << _amount << endl;
}
int main()
{
    /*
    CGoods可以定义无数的对象,每一个对象都有自己的成员变量
    但是他们共享一套成员方法
    */
    /*
    show()  =>  怎么知道处理哪个对象信息?
    init(name,price,amount) =>  怎么知道把信息初始化给哪一个对象?

    类的成员方法一经编译,所有的成员方法参数,都会加一个this指针,接受调用该方法的对象的地址。用this指针来区分调用该成员方法的不同对象。
    */
    CGoods good1;//类实例化了一个对象

    //init(&good1,"面包", 10.0, 200)
    good1.init("面包", 10.0, 200);
    //show(&good1)
    good1.show();

    good1.setPrice(20.5);
    good1.setAmount(100);
    good1.show();

    CGoods good2;//类实例化了一个对象
    good2.init("空调", 10000.0, 50);
    good2.show();

    return 0;

}

运行结果
在这里插入图片描述
对象占用内存的大小,只和成员变量有关(不考虑static成员变量),与成员方法无关

查看对象占用内存大小:打开vs工具->visual studio命令提示,cd test.cpp所在目录,输入
cl test.cpp /d1reportSingleClassLayoutCGoods
在这里插入图片描述
this指针的作用:

一个类产生了很多对象,每一个对象都有自己的成员变量,但是同一个类型的对象共享一套成员方法。那么一套成员方法如何区分不同的对象呢?就是通过this指针来区分的,成员方法一经编译,方法的参数都会添加一个this指针,用来接收调用该方法的对象。所以在成员方法里访问的其他成员变量或调用其他的成员方法前面都会默认加this指向。

二、掌握构造函数和析构函数

构造函数:定义对象时,自动调用的;可以重载的;构造完后,对象产生了

析构函数:不带参数,不能重载,只有一个析构函数;析构完成后,对象就不存在,但内存还在。

#include <iostream>
using namespace std;


class SeqStack
{
public:
    SeqStack(int size = 10)//是可以带参数的,因此可以提供多个构造函数,叫做构造函数的重载
    {
        cout << this << "SeqStack()" << endl;
        _pstack = new int[size];
        _top = -1;
        _size = size;
    }
    //析构函数
    ~SeqStack()//是不带参数的,所有析构函数只能有一个
    {
        cout << this << "~SeqStack()" << endl;
        delete[]_pstack;
        _pstack = nullptr;
   }
    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];
        }
        delete[]_pstack;
        _pstack = ptmp;
        _size *= 2;
    }
};
SeqStack gs;//全局对象,程序结束后析构
int main()
{
    /*
    .data//定义的对象在程序结束被析构
    heap//new的时候构造,delete的时候析构
    stack//定义的对象在出函数就会被析构
    */
    SeqStack* ps = new SeqStack(60);//malloc内存开辟+SeqStack对象构造
    ps->push(70);
    ps->push(80);
    ps->pop();
    cout << ps->top() << endl;
    delete ps;//先调用ps->~SeqStack()+然后free(ps)

    //1.开辟内存;2.调用构造函数
    SeqStack s;
    for (int i = 0; i < 15; i++)
    {
        s.push(rand() % 100);

    }
    while (!s.empty())
    {
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;
    SeqStack s1(50);
    //s1.~SeqStack()//析构函数调用后对象不存在了,就不要再调用它的方法了
    return 0;

}

三、对象的深拷贝和浅拷贝

拷贝构造:是在用已存在的对象构造新生成的对象的时候自动调用;

赋值函数:用已存在的对象给已存在的对象进行赋值的过程自动调用的函数。

对象默认的拷贝构造和赋值函数都是做内存的数据拷贝,也就是浅拷贝。关键是对象如果占用外部资源,那么浅拷贝就出现问题了!析构时会对同一资源进行多次释放,后析构的就会出错!

举例代码:

(1)栈

#include <iostream>
using namespace std;

class SeqStack
{
public:
    SeqStack(int size = 10)
    {
        cout << this << "SeqStack()" << 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;
    }
    //赋值重载函数
    void operator=(const SeqStack& src)
    {
        cout << "operator=(const SeqStack& src)" << endl;
        //防止自赋值
        if (&src == this)
        {
            return;
        }
        //需要先释放当前对象占用的外部资源
        delete[]_pstack;
        _pstack = new int[src._size];
        for (int i = 0; i <= src._top; i++)
        {
            _pstack[i] = src._pstack[i];
        }//做深拷贝
        //realloc和memcpy(_pstack,src._pstack,sizeof(int)*_size//做浅拷贝,不建议用
        _top = src._top;
        _size = src._size;
    }
    //析构函数
    ~SeqStack()
    {
        cout << this << "~SeqStack()" << endl;
        delete[]_pstack;
        _pstack = nullptr;
   }
    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];
        }
        delete[]_pstack;
        _pstack = ptmp;
        _size *= 2;
    }
};
int main()
{
    SeqStack s;//没有提供任何构造函数的时候,会为你生成默认构造和默认析构
    SeqStack s1(10);
    SeqStack s2 = s1;//默认拷贝构造函数=》做直接内存数据拷贝
    //SeqStack s2(s1);

    s2 = s1;//默认的赋值函数=》做直接内存拷贝
    return 0;
}

运行结果:
在这里插入图片描述
(2)string类

#include <iostream>
using namespace std;

class String
{
public:
    String(const char* str = nullptr)
    {
        if (str != nullptr)
        {
            m_data = new char[strlen(str) + 1];
            strcpy(m_data, str);
        }
        else
        {
            m_data = new char[1];
            *m_data = '\0';
        }
    }
    String(const String& src)
    {
        m_data = new char[strlen(src.m_data) + 1];
        strcpy(m_data, src.m_data);
    }
    ~String()
    {
        delete[]m_data;
        m_data = nullptr;
    }

	//调用String&是为了支持连续的operator=赋值操作
    String& operator=(const String& src)
    {
        if (&src == this)
        {
            return *this;
        }
        delete[]m_data;
        m_data = new char[strlen(src.m_data) + 1];
        strcpy(m_data, src.m_data);
        return *this;
    }
private:
    char* m_data;//用于保存字符串
};
int main()
{
    //调用构造函数
    String str1;
    String str2("hello");
    String str3 = "world";

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

    //调用赋值重载函数
    str1 = str2;
    return 0;
}

(3)循环队列

#include <iostream>
using namespace std;

class Queue
{
public:
    Queue(int size = 10)
    {
        _pQue = new int[size];
        _front = _rear = 0;
        _size = size;
    }
    Queue(const Queue& src)
    {
        _front = src._front;
        _rear = src._rear;
        _size = src._size;
        _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 (&src == this)
        {
            return *this;
        }
        delete[]_pQue;
        _front = src._front;
        _rear = src._rear;
        _size = src._size;
        _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];
            index++;
        }
        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() << endl;
        queue.pop();
    }
    cout << endl;

    Queue queue1 = queue;
    return 0;
}

四、构造函数的初始化列表

#include <iostream>
using namespace std;

class CDate
{
public:
    CDate(int y, int m, int d)//自定义了一个构造函数,编译器就不会再产生默认构造函数了
    {
        _year = y;
        _month = m;
        _day = d;
    }
    void show()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
//构造函数的初始化列表:可以指定当前对象成员变量的初始化形式
//CDate信息是CGoods信息的一部分	a part of...组合关系
class CGoods
{
public:
    //"CDate":没有合适的默认构造函数可用
    CGoods(const char* n, int a, int p, int y, int m, int d)
        //构造函数的初始化列表
        :_date(y,m,d)
        ,_amount(a)//int _amount = a
        ,_price(p)
    {
        //当前类类型构造函数体
        strcpy(_name, n);
        //_amount = a;//int _amount;_amount=a;
    }
    void show()
    {
        cout << "name:" << _name << endl;
        cout << "amount:" << _amount << endl;
        cout << "price:" << _price << endl;
        _date.show();
    }
private:
    char _name[20];
    int  _amount;
    double _price;
    CDate _date;//成员对象  1.分配内存  2.调用构造函数
};
int main()
{
    CGoods good("商品1", 100, 35.0, 2019, 5, 21);
    good.show();
    return 0;
}

成员变量的初始化和它们定义的顺序有关,和构造函数初始化列表中出现的先后顺序无关!

举例:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int data = 10) :mb(data), ma(mb) {}
    void show() { cout << "ma:" << ma << endl << "mb:" << mb << endl; }
private:
    int ma;
    int mb;
};
int main()
{
    Test t;//0xcccccccc -858993460
    t.show();
    return 0;
}

运行结果:
在这里插入图片描述

五、类的各种成员方法及区别

类的各种成员方法 - 成员方法/变量

1.普通的成员方法=>编译器会添加一个this形参变量

(1)属于类的作用域

(2)调用该方法时,需要依赖一个对象

(3)可以任意访问对象的私有成员

2.static静态成员方法=>不会生成this形参

(1)属于类的作用域

(2)用类名作用域来调用方法

(3)可以任意访问对象的私有成员,仅限于不依赖对象的成员(只能调用其他的static静态成员)

3.const常成员方法=>const CGoods *this

(1)属于类的作用域

(2)调用依赖于一个对象,普通对象或者常对象都可以

(3)可以任意访问对象的私有成员,但是只能读,而不能写

#include <iostream>
using namespace std;

#if 1
class CDate
{
public:
    CDate(int y, int m, int d)
    {
        _year = y;
        _month = m;
        _day = d;
    }
    void show()const
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
class CGoods
{
public:
    CGoods(const char* n, int a, int p, int y, int m, int d)
        :_date(y,m,d)
        ,_amount(a)
        ,_price(p)
    {
        strcpy(_name, n);
        _count++;//记录所有产生的新对象的数量
    }
    //普通成员方法
    void show()//打印商品的私有信息CGoods *this
    {
        cout << "name:" << _name << endl;
        cout << "amount:" << _amount << endl;
        cout << "price:" << _price << endl;
        _date.show();
    }
    //常成员方法  只要是只读操作的成员方法,一律实现成const常成员方法
    void show()const//const CGoods *this
    {
        cout << "name:" << _name << endl;
        cout << "amount:" << _amount << endl;
        cout << "price:" << _price << endl;
        _date.show();
    }
    //静态成员方法,没有this指针的
    static void showCGoodsCount()//打印的是所有商品共享的信息
    {
        cout << "所有商品的种类数量是:" << _count << endl;
    }
private:
    char _name[20];
    int  _amount;
    double _price;
    CDate _date;
    static int _count;//不属于对象,而是属于类级别的  声明 用来记录商品对象的总数量
};
//static成员变量一定要在类外进行定义并且初始化
int CGoods::_count = 0;
int main()
{
    CGoods good1("商品1", 100, 35.0, 2019, 5, 21);
    good1.show();

    CGoods good2("商品2", 100, 35.0, 2019, 5, 21);
    good2.show();

    CGoods good3("商品3", 100, 35.0, 2019, 5, 21);
    good3.show();

    CGoods good4("商品4", 100, 35.0, 2019, 5, 21);
    good4.show();
    
    //统计所有商品的总数量
    CGoods::showCGoodsCount();
	
	const CGoods good5("非卖品商品5",100,35.0,2019,5,21);
	good5.show();
    return 0;
}
#endif 

运行结果:
在这里插入图片描述

六、指向类成员的指针

#include <iostream>
using namespace std;

class Test
{
public:
    void func() { cout << "call Test::func" << endl; }
    static void static_func() { cout << "Test::static_func" << endl; }
    int ma;
    static int mb;
};
int Test::mb;
int main()
{
    Test t1;
    Test* t2 = new Test();

    //指向成员方法的指针
    //无法从"void(_thiscall Test::*)(void)"转换为"void (_cdecl*)(void)"
    void (Test:: * pfunc)() = &Test::func;
    (t1.*pfunc)();
    (t2->*pfunc)();
    //指向static成员方法的指针
    void (*static_func)() = &Test::static_func;
    Test::static_func();

    //无法从"int Test::*"转换为"int*"
    int Test::* p = &Test::ma;
    t1.*p = 20;
    cout << t1.*p << endl;

    t2->*p = 30;
    cout << t2->*p << endl;

    int* p1 = &Test::mb;
    *p1 = 40;
    cout << *p1 << endl;

    delete t2;
    return 0;
}

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-15 15:19:21  更:2021-08-15 15:22:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 4:44:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码