C++梁哥笔记day6 | 我的日常分享

C++梁哥笔记day6

类与对象

一、类的扩展

  1. 类的大小
    成员函数不占用类的大小 成员函数在代码区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Data{
private:
int num;
public:
int getNum(){
return num;
}
void setNum(int n){
num = n;
}
};
void test01(){
cout<<"sizeof(Data):"<<sizeof(Data)<<endl;
}

运行结果:
图片

  1. 在类里声明成员函数 在类外定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Data{
private:
int num;
public:
int getNum();
void setNum(int n);
};
int Data::getNum(){
return num;
}
void Data::setNum(int n){
num = n;
}
void test02(){
Data d1;
d1.setNum(100);
cout<<"num:"<<d1.getNum()<<endl;
}

运行结果:
图片

  1. 分文件实现类(类的定义在头文件,成员函数在cpp中实现)
    图片

data.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef DATA_H
#define DATA_H

class Data
{
private:
int num;
public:
int getNum();
void setNum(int n);
};

#endif // DATA_H

data.cpp

1
2
3
4
5
6
7
8
9
#include "data.h"

int Data::getNum(){
return num;
}
void Data::setNum(int n){
num = n;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
#include "data.h"
void test03(){
Data d1;
d1.setNum(100);
cout<<"num:"<<d1.getNum()<<endl;
}
int main(int argc, char *argv[])
{
test03();
return 0;
}

运行结果:
图片


二、对象的构造和析构

构造函数和析构函数,这两个函数将会被编译器自动调用,构造函数完成对象的初始化动作,析构函数在对象结束的时候完成清理工作。
注意:对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认的初始化操作不会做任何事情。(空的函数)

1. 构造函数和析构函数的概述

构造函数主要用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调动,无需手动调用。析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。

构造函数:实例化对象的时候系统自动调用。
析构函数:对象释放的时候系统自动调用。

2. 构造函数和析构函数的定义

构造函数的语法:构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数,可以重载。 ClassName(){}

析构函数的语法:析构函数函数名是在类名前加"~"组成,没有返回值,不能有void,不能有参数,不能进行函数重载 ~ClassName(){}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Data1
{
private:
int num;
public:
Data1(){
num = 0;
cout<<"无参构造函数"<<endl;
}
Data1(int n){
num = n;
cout<<"带参构造函数"<<endl;
}
~Data1(){
cout<<"析构函数"<<endl;
}
int getNum(){
return num;
}
void setNum(int n){
num = n;
}
};
void test10(){
Data1 num1;
}

int main(int argc, char *argv[])
{
test10();
return 0;
}

运行结果:
图片

3.构造函数的分类以及调用

  1. 构造函数的分类
  • 按参数类型:分为无参构造函数和有参构造函数
  • 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
  1. 构造函数的调用
  • 无参构造的调用形式:
    • 实例化对象时:
      • 隐式调用:Data d1;
      • 显示调用:Data d2 = Data();
  • 有参构造的调用形式:
    • 实例化对象时:
      • 隐式调用:Data d3(10);
      • 显示调用:Data d4 = Data(20);
      • 隐式转换的方式(针对于只有一个数据成员):Data d5 = 30;//转化成Data d5(30)(尽量不用)
      • 匿名对象(当前语句结束,匿名对象立即释放):Data(50);

千万不要以以下代码这种方式调用无参构造函数:

1
2
3
4
int main(){
Data d1();//编译器不会认为是实例化对象d1,而是看成了函数d1的声明
return 0;
}

注意:在同一作用域中,构造和析构的顺序相反,即最先构造的最后析构(释放)

4.拷贝构造函数的调用

  1. 拷贝构造函数
    1
    2
    3
    4
    5
    //拷贝构造函数
    Data(const Data &d){
    cout<<"拷贝构造函数"<<endl;
    num = d.num;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class Data
    {
    private:
    int num;
    public:
    Data(){
    num = 0;
    cout<<"无参构造函数"<<endl;
    }
    Data(int n){
    num = n;
    cout<<"带参构造函数"<<endl;
    }
    ~Data(){
    cout<<"析构函数"<<endl;
    }
    //拷贝构造函数
    Data(const Data &d){
    cout<<"拷贝构造函数"<<endl;
    }
    int getNum(){
    return num;
    }
    void setNum(int n){
    num = n;
    }
    };

如果用户没有定义拷贝构造函数,系统会提供一个默认的拷贝构造函数,进行的赋值操作(浅拷贝)

1
2
3
4
5
6
7
8
9
10
11
void test11(){
Person lucy;
lucy.setAge(20);
lucy.setName("lucy");
//如果用户没有实现构造函数,系统将调用默认的拷贝构造函数
//默认的拷贝构造函数:淡出的整体赋值(浅拷贝)
//如果用户实现了拷贝构造函数,系统将调用用户实现的拷贝构造函数,不会调用默认的拷贝构造函数了
Person bob = lucy;
lucy.showPerson();
bob.showPerson();
}

运行结果:
图片

  1. 拷贝构造函数的调用形式
  • 隐式调用拷贝构造函数:Person bob(lucy);
  • 显式调用拷贝构造函数:Person bob = Person(lucy);
  • 隐式转换调用:Person bob = lucy;(用的最多)

什么时候会调用拷贝构造函数:记住一句话,旧对象初始化为新对象,才会调用拷贝构造函数。

下面的代码就不会调用拷贝构造函数:只是赋值

1
2
3
4
5
6
7
8
9
void test11(){
Person lucy;
lucy.setAge(20);
lucy.setName("lucy");
Person bob;
bob = lucy;
lucy.showPerson();
bob.showPerson();
}

运行结果:
图片

  1. 拷贝构造函数的注意事项:

    • 不能调用拷贝构造函数去初始化匿名对象,也是就说以下代码不正确

      1
      2
      3
      4
      5
      6
      void test12(){
      Data d1(10);//有参构造

      //调用不了拷贝构造函数 错
      Data(d1);//Data(d1) ==> Data d1;造成d1重定义
      }
    • 对象作为函数的参数,如果实参与形参都是普通对象,那么会调用拷贝构造函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //函数的形参是在函数调用的时候开辟空间
      //此处的ob 就会调用拷贝构造
      void myPrintData(Data ob){//Data ob = ob1;
      cout<<"num = "<<ob.num<<endl;
      }
      void test13(){
      Data ob1(10);
      myPrintData(ob1);
      }
    • 函数返回局部对象,在qtcreate中会被编译器优化,从而调用不了拷贝构造函数

      1
      2
      3
      4
      5
      6
      7
      8
      Data returnData(){
      Data ob1(10);//调用有参构造函数
      return ob1;
      }
      void test14(){
      Data ob2 = returnData();
      //实际上是ob2指向了ob1的空间,ob2的引用等于ob1的引用,并没有调用拷贝构造函数
      }