文章目录
注:转载请标明原文出处链接: https://lvxiaowen.blog.csdn.net/article/details/107307557
1 对象结构
1.1 内存分区
栈区:内存由系统来控制,无论是分配还是回收都不需要程序员关心;
堆区:内存需要程序员管理;
全局区:存储全局变量及静态变量;
常量区:存储字符串和常量;
代码区:存储编译之后的二进制代码。
1.2 实例化的对象如何在内存中存储的?
类在实例化前不占用堆或栈的内存,类实例化后每个对象都在栈上开辟内存来存储自己的数据。
2 构造函数
2.1 对象初始化
2.1.1 对象初始化概念
对象初始化分类:(1)有且仅有一次的初始化;(2)根据条件进行初始化。
例如每次开始玩坦克大战时坦克和炸弹都放在固定位置,即对象初始化。
2.1.2 初始化方式
类内成员变量一般有四种:一般变量、静态成员变量(static)、常量(const)、静态常量(static const),对应初始化方式如下:
(1) 一般变量可以在初始化列表里或者构造函数里初始化,不能直接初始化或者类外初始化
(2) 静态成员变量必须在类外初始化
(3) 常量必须在初始化列表里初始化
(4) 静态常量必须只能在定义的时候初始化(定义时直接初始化)
2.2 构造函数
为了避免对象初始化使用产生误操作(重复初始化,忘记使用等误操作),推出了构造函数。
构造函数是一种特殊的方法,主要用来在创建对象时初始化对象,即为对象成员变量赋初始值。
2.2.1 构造函数的规则和特征
(1) 构造函数在对象实例化时被自动调用;
(2) 构造函数与类名同名;
(3) 构造函数没有返回值;
(4) 构造函数可以有多个重载形式;
(5) 实例化对象时即使有多个构造函数,也仅仅用到一个构造函数;
(6) 当用户没有定义构造函数时,编译器自动生成一个构造函数。
2.2.2 构造函数的定义
(1)无参构造函数定义:构造函数与类名相同,没有返回值。
(2)有参构造函数
3)重载构造函数
2.2.3 默认构造函数
默认构造函数:实例化对象时不需要传入参数的构造函数。
2.2.4 构造函数初始化列表
初始化列表的特性
(1) 初始化列表先于构造函数执行
(2) 初始化列表只能用于构造函数
(3) 初始化列表可以同时初始化多个数据成员
哪几种情况必须用到初始化成员列表?
(1) 初始化一个const成员。因为构造函数的函数体内只能做赋值而不是初始化。
(2) 初始化一个引用成员。因为构造函数的函数体内只能做赋值而不是初始化。
//初始化一个const成员和引用成员
#include <iostream>
using namespace std;
class CircleArea
{
public:
CircleArea(int &r) : m_ir(r), m_dpi(3.14) {}
void print_area()
{
cout << "When radius = "<< m_ir <<", the area of the circle = " << m_ir * m_ir * m_dpi << endl;
}
private:
int &m_ir;
const double m_dpi;
};
int main()
{
int r = 5;
CircleArea c(r);
c.print_area();
system("pause");
return 0;
}
(3) 类成员为没有默认构造函数的类类型。
//类成员为没有默认构造函数的类类型
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a)
{
val = a;
}
private:
int val;
};
class A
{
public:
A(int v) : p(v), b(v) {} //此时必须使用初始化列表
void print_val() { cout << "hello:" << p << endl; }
private:
int p;
Base b;//类成员为没有默认构造函数的类类型
};
int main()
{
char p = 45;
A b(p);
b.print_val();
system("pause");
return 0;
}
(4) 如果类存在继承关系,派生类必须在其初始化列表中调用基类的构造函数。
//派生类必须在其初始化列表中调用基类的构造函数
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a) : val(a) {}
private:
int val;
};
class A : public Base
{
public:
A(int v) : p(v), Base(v) {}//必须使用初始化列表
void print_val() { cout << "hello:" << p << endl; }
private:
int p;
};
int main()
{
int pp = 45;
A b(pp);
b.print_val();
}
2.2.5 拷贝构造函数
下图程序中,实例化了3个对象,理论上应该调用3次构造函数,但实际上只调用了一次。
上图程序确实调用了构造函数,但调用的是拷贝构造函数。
拷贝构造函数:类名(const 类名& 变量名)
其中&变量名
可以省略
拷贝构造函数的规则和特征
(1) 如果没有自定义的拷贝构造函数则系统自动生成一个默认的拷贝构造函数。
(2) 当采用直接初始化或复制初始化实例化对象时系统自动调用拷贝构造函数。
调用拷贝构造函数的情况
(1) 对象作为函数的参数,以值传递的方式传给函数。
(2) 使用一个对象给另一个对象初始化
(3) 对象作为函数的返回值,以值的方式从函数返回;返回时,又调用了一次赋值构造函数,相当于又定义一个对象,然后把值赋值给最后这个赋值构造函数,也就是临时对象。
示例
题目描述:
定义Teacher类
(1)自定义拷贝构造函数
(2)数据成员:名字,年龄
(3)成员函数:数据成员的封装函数
Teacher.h
#ifndef TEACHER_H
#define TEACHER_H
#include<iostream>
using namespace std;
class Teacher
{
public:
Teacher(string name = "Jim", int age = 1);
Teacher(const Teacher &t);
void setName(string name);
string getName();
void setAge(int age);
int getAge();
string m_sName;
int m_iAge;
};
#endif
Teacher.cpp
#include"Teacher.h"
#include<string>
using namespace std;
Teacher::Teacher(string name, int age)
{
m_sName = name;
m_iAge = age;
cout<<"Teacher(string name, int age)"<<endl;
}
Teacher::Teacher(const Teacher &t)
{
cout << "Teacher(const Teacher &t)" << endl;
}
void Teacher::setName(string name)
{
m_sName = name;
}
string Teacher::getName()
{
return m_sName;
}
void Teacher::setAge(int age)
{
m_iAge = age;
}
int Teacher::getAge()
{
return m_iAge;
}
main.cpp
#include <iostream>
#include"Teacher.h"
using namespace std;
void test(Teacher t) {}
Teacher a;
Teacher testfun()
{
return a;
}
int main()
{
cout << endl;
//1个对象给另1个对象初始化,调用了拷贝构造函数
Teacher t1;
Teacher t2 = t1;//初始化
Teacher t3(t1);//初始化
cout << endl;
//赋值语句,不会调用拷贝构造函数
Teacher t4,t5;
t5 = t4;
cout << endl;
//对象作为函数的参数,以值传递的方式传给函数
test(t1);//相当于将t1复制给test(t)中的t,导致调用复制构造函数
cout << endl;
//对象作为函数的返回值,函数返回时调用拷贝构造函数。
testfun();
cout << endl;
system("pause");
return 0;
}
运行结果:
赋值运算符和拷贝构造函数
相同点:都是将一个对象的值复制给另一个对象;
不同点:拷贝函数要新建立一个对象,赋值运算符是将对象的值复制给一个已经存在的实例。
2.2.6 构造函数总结
3 析构函数
如果说构造函数是对象来到人间的第一声哭泣,析构函数则是对象的临终遗言。
3.1 析构函数定义
格式:~类名()
析构函数在对象销毁时自动调用,其目的是归还系统资源。
3.2析构函数特性
(1) 如果没有自定义的析构函数则系统自动生成;
(2) 析构函数在对象销毁时自动调用;
(3) 析构函数没有返回值和参数,不能重载
3.3示例
问题描述:
定义Teacher类
(1)自定义析构函数
(2)普通方式实例化的对象,在销毁对象时是否自动调用析构函数
(3)通过过将拷贝构造函数实例化的对象,在销毁对象时是否自动调用析构函数
(4)数据成员:名字,年龄
(5)成员函数:数据成员的封装函数
Teacher.h
#ifndef TEACHER_H
#define TEACHER_H
#include <iostream>
#include <string>
using namespace std;
class Teacher
{
public:
Teacher(string name = "Jim", int age = 1);//构造函数
Teacher(const Teacher &tea);//拷贝构造函数
~Teacher();//析构函数
void setName(string name);
string getName();
void setAge(int age);
int getAge();
private:
string m_strName;
int m_iAge;
};
#endif
Teacher.cpp
#include <iostream>
#include <string>
#include "Teacher.h"
using namespace std;
Teacher::Teacher(string name, int age) :m_strName(name), m_iAge(age)
{
cout << "Teacher(string _name, int _age)" << endl;
}
Teacher::Teacher(const Teacher &tea)
{
cout << "Teacher(const Teacher &tea)" << endl;
}
Teacher::~Teacher()
{
cout << "~Teacher()" << endl;
}
void Teacher::setName(string name)
{
m_strName = name;
}
string Teacher::getName()
{
return m_strName;
}
void Teacher::setAge(int age)
{
m_iAge = age;
}
int Teacher::getAge()
{
return m_iAge;
}
main.cpp
#include <iostream>
#include"Teacher.h"
using namespace std;
int main()
{
Teacher t1;
Teacher *p = new Teacher();
delete p;
cout << endl;
Teacher t2(t1);
system("pause");
return 0;
}
运行结果:
4 对象的生命历程
参考资料
[1] https://www.imooc.com/learn/381
[2] https://www.cnblogs.com/dobben/p/7501106.html