C++--之四(类和对象)

原创 2017年01月02日 23:29:48

###还是用代码来作为笔记吧,写的比较乱,见谅啊!

#ifndef _CLASSDEMO_H_
#define _CLASSDEMO_H_


namespace PoEdu
{
    class ClassDemo
    {
    public:
        ClassDemo();
        ClassDemo(int num);
        ClassDemo &operator=(const ClassDemo &other);
        ClassDemo(const ClassDemo &other);  // 拷贝构造函数,它的格式是固定的,它的格式无法发生改变,所以它是无法重载的
                                            // 那么我们不写,编译器会默认帮我们生成一个,如果写了,编译器就不再生成了

        //// 如果我们写了这样一个函数,那么就永远无法通过编译的,会涉及到一个东西是 无限递归
        //// 那么为什么会进行无限递归呢?原因很简单,因为形参在被调用的时候,要被实参化,实参化就是进行一个拷贝,那么在拷贝的时候
        //// 就会调用一个构造函数(即拷贝构造函数,那么拷贝构造函数就是它本身),所以就变成了无限的递归
        //ClassDemo(const ClassDemo other)
        //{
        //  
        //}

        ~ClassDemo();

        int GetNum() const;

    private:
        int _num;
    };
}

#endif // !_CLASSDEMO_H_
#include "ClassDemo.h"
#include <iostream>


namespace PoEdu
{
    ClassDemo::ClassDemo()
    {
        std::cout << "ClassDemo(" << _num << ")" << std::endl;
    }
    // 初始化列表
    ClassDemo::ClassDemo(int num) : _num(num)
    {
        std::cout << "ClassDemo(" << num << ")" << std::endl;
    }

    ClassDemo& ClassDemo::operator=(const ClassDemo& other)
    {
        std::cout << "operator=(const ClassDemo& other)" << _num << std::endl;
        _num = other._num;

        return *this;
    }

    ClassDemo::ClassDemo(const ClassDemo& other)
    {
        std::cout << "ClassDemo(const ClassDemo& other)" << _num << std::endl;
        _num = other._num;
    }

    ClassDemo::~ClassDemo()
    {
        std::cout << "~ClassDemo(" << _num << ")" << std::endl;
    }

    int ClassDemo::GetNum() const
    {
        return _num;
    }
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "ClassDemo.h"


//// 空类
//class AAA
//{
//  // 默认构造         AAA(){}
//  // 默认析构         ~AAA(){}
//  // 默认拷贝构造       AAA(const AAA& other){}             // 这个函数就是一个空函数,只是针对于这个空类来说的
//  // 默认赋值         AAA &operator=(const AAA &other){ return *this; }   // 同样也是什么也不做
//};


//// 不是空类
//class AAA
//{
//public:
//  // 默认构造         AAA(){} 没有发生改变
//  AAA(int num, int other) // 此时默认构造函数就没有了
//  {
//      _num = num;
//      _other = other;
//  }
//  // 默认析构         ~AAA(){}   没有发生改变
//  // 默认拷贝构造       AAA(const AAA& other){_num = other._num; _other = otehr._other;}    // 这个函数就是一个空函数,只是针对于这个空类来说的     发生改变了
//                                                          // 因为拷贝构造函数是一个功能函数
//  // 默认赋值         AAA &operator=(const AAA &other){ _num = other._num; _other = otehr._other; return *this; } // 同样也是什么也不做        发生改变了
//
//  void SetNum(int num)
//  {
//      _num = num;
//  }
//  int GetNum()
//  {
//      return _num;
//  }
//private:
//  int _num;
//  int _other;
//};


class MyString
{
public:
    // 默认构造函数
    MyString()
    {
        _len = 100;
        _str = new char[_len];
    }
    MyString(char *str)
    {
        _len = strlen(str);
        _str = new char[_len + sizeof(char)];
        strcpy(_str, str);
    }
    // 默认析构函数
    ~MyString()
    {
        if (_str)
            delete[]_str;
    }
    // 默认拷贝构造函数
    // MyString(const MyString& other){_str = other._str; }
    // 默认赋值函数
    // MyString &MyString(cosnt MyString &other){ _str = other._str; }
    // 那么此时的默认拷贝构造和默认的赋值函数就开始变得危险了,需要自己实现了

    MyString(const MyString &other)
    {
        // 对象分为两种
        // 1.面向对象
        // 2. 使用对象

        // 这种我们称之为深拷贝,这样才是安全的
        // 深拷贝指的是在对象中,维护了所有参数的生命周期,所有参数的生命周期和我的对象的生命周期是一致的
        delete[] _str;
        _len = other._len;
        _str = new char[_len + sizeof(char)];
        strcpy(_str, other._str);
        // 这种直接对指针赋值的行为我们称为浅拷贝,是存在风险的(当被delete的时候)
        // 没有维护参数的生命周期,是由外部来维护的
        //_str = other._str;
        //_len = other._len;

        // 但是如果没有指针的话,深拷贝和浅拷贝是一样的
        // 我们并不是说深拷贝就一定比浅拷贝好,需要根据我们的业务来定
        // 有的时候我们是不需要维护对象参数的生命周期的
    }
    MyString & operator=(const MyString &other)
    {
        delete[] _str;
        _len = other._len;
        _str = new char[_len + sizeof(char)];
        strcpy(_str, other._str);

        return *this;

        //_str = other._str;
        //_len = other._len;
    }

    char *GetString()
    {
        return _str;
    }
private:
    char *_str;
    int _len;
};


void f1(PoEdu::ClassDemo demo)
{
    demo.GetNum();
}

void f2(const PoEdu::ClassDemo &demo)
{
    demo.GetNum();
}

// 上面的两个函数在使用上没有任何区别,但是在参数传递的时候,就会有区别
// f1产生了临时对象,并且销毁了临时对象,所以在函数内部使用的也是临时对象 
// 然而f2没有产生临时对象,使用的外部对象
// 说明了传递引用会更加节约时间,提高效率,但是这样的引用传递的风险也大
// 那么怎么办呢?应该加上const,但是,const对象的引用在调用函数的时候,有一个限制条件
// 那就是必须调用常成员函数


int main()
{
    using namespace PoEdu;


    // 第3.4节课 测试3.3节课的内容
    //ClassDemo demo = 10;  // 相当于ClassDemo demo(10);
    // A 构造函数 (更详细的说应该是转换构造函数)
    // B 隐藏的operator=函数


    // 但凡有一个新的对象的产生,100%就需要调用构造函数
    //demo = 20;    // 正确答案是 C 
    // A operator=
    // B temp
    // C temp 默认的operator=
    // 然而,当我们加上ClassDemo &operator=(const int other)这个函数之后,那么上面的答案就会发生改变,答案就会变成A
    // 为什么会这样呢?因为我们重载的ClassDemo &operator=(const int other)这个函数和demo=20是最匹配的,所以就会先调用
    // 这个函数了,这和我们的重载函数的调用是一样的
    //ClassDemo demo1 = demo;   // 这句话说明我们肯定有一个ClassDemo(ClassDemo)的函数,否则的话,肯定编译不通过的
                            // 然而我们并没有写这个函数,那就是编译器帮我们生成了这个函数了


    //ClassDemo demo(10);
    //f1(demo);
    //f2(demo);

    //AAA aaa, bbb;
    //aaa.SetNum(10);
    //bbb = aaa;    // 是一个赋值函数
    //          // 但是这句话能够编译通过是因为有一个默认的AAA &operator=(const AAA &other){ return *this; }函数
    //std::cout << bbb.GetNum() << std::endl;

    //AAA aaa;
    //aaa.SetNum(10);
    //AAA bbb = aaa;    // 是一个默认构造函数
    //std::cout << bbb.GetNum() << std::endl;
    // 上面的例子说明了默认的拷贝构造函数和默认的赋值函数都是功能型函数

    MyString str("I Love Mark!!!");
    MyString sb = str;  //拷贝构造
    std::cout << sb.GetString() << std::endl;
    sb = "I Need Mark!!!";
    // 上面会做三件事情
    // 1.构造临时对象 temp
    // 2. operator=(const MyString& other) { sb._str = other._str; _len = other._len; } 这里的other就是temp
    // 3. 析构temp,那么第二步的操作就会变得要命了  delete temp._str  delete sb._str(野指针)
    std::cout << sb.GetString() << std::endl;
    // 使用野指针是一种未定义的行为
    // debug 下面的 0xCCCCCCCC是未经过初始化的值
    // 0xDDDDDDDD是已经删除的值    这些都是一般性的,不是百分之百的

    // 而在release版本下面就是随机的

    return 0;
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

C++第八周【任务四】分数类中的对象可以和整型数进行四则运算,且运算符合交换律。

/* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2011, 烟台大学计算机学院学生 * All rights reserved. * 文件名称 :C++第八...

C++面向对象编程(四)友元函数和友元类

友元函数: 声明:friend 返回类型 函数名(形参表) { ...... //函数体 } 说明: 友元函数可以是另一个类的成员函数,也可以是不属于任何类的一般的函数。 友元是一个普通的函数,它不是...

C++ 知识点(四):面向对象编程:多态,虚函数,数据抽象,数据封装,抽象类

C++ 多态多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。下面的实例中,基...
  • Jurbo
  • Jurbo
  • 2016-09-06 21:24
  • 1026

C++类与对象PPT

  • 2015-08-31 17:09
  • 1.32MB
  • 下载

类和对象c++

  • 2013-06-23 19:46
  • 900B
  • 下载

C++知识点总结(四)——面向对象的编程细节总结

1.空类的默认函数        一般情况下,对于任意一个类A,如果程序员不显示的声明和定义上述函数,C++编译器将会自动的为A产生4个public inline(公有、内联)的默认函数,这4个...

谭浩强c++类和对象

  • 2014-09-03 06:19
  • 502KB
  • 下载

C++ppt-类与对象

  • 2014-02-10 22:24
  • 416KB
  • 下载

Linux Debugging(四): 使用GDB来理解C++ 对象的内存布局(多重继承,虚继承)

关于C++虚函数,很多博文从各个角度来探究虚函数是如何实现的,或者说编译器是如何实现虚函数的。本文通过GDB来从另外一个角度来理解C++ object的内存布局,一来熟悉语言背后编译器为了实现语言特性...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)