目录
1 构造函数
1.1 类对象如何初始化
如果类的数据成员是public的权限,可以在定义对象时初始化。
class Time
{
public:
int hour;
int min;
int sec;
};
Time t1 = {24,0,0}; //与结构体的初始化方式类似
数据成员的权限如果是private和protected,就无法使用这种方式。
类对象的初始化任务,只能由成员函数来完成。
1.2 构造函数的定义
C++利用类的构造函数来初始化类对象的数据成员。
Time(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
Time t1(20,12,35);
在创建类对象t1时自动调用构造函数,对其数据成员初始化。
1.3 构造函数的知识要点
(1)构造函数的函数名必须与类名相同
(2)构造函数可以没有形参,也可以定义多个形参。即构造函数可以被重载,实参的类型和个数决定调用哪个构造函数。
(3)构造函数不能指定返回类型,不返回任何值。即它既不属于返回值函数也不属于void函数。
(4)构造函数是一种特殊的成员函数,构造函数不需要使用者主动调用,而是建立对象时自动执行。
2 构造函数的初始化列表
2.1 初始化列表的形式
构造函数与其他函数不同的是,可以包含一个初始化列表。
class Student
{
int m_age;
string m_name;
char m_sex;
};
Student::Student(int age,string name,char sex):m_age(age),m_name(name),m_sex(sex){}
2.2 初始化与赋值的区别
明确初始化的意义:
初始化和赋值的意义不同。
初始化是指创建对象的同时给它赋初值。
赋值是擦除对象的当前值并用新值代替。
int i = 50; //初始化
i = 60; //赋值
2.3 初始化列表与函数体内赋值的区别
使用初始化列表初始化数据成员与构造函数体内赋值的效果是相同的,不同之处在于:
(1)初始化列表是执行初始化过程,在创建数据成员的同时给它们赋初值。
(2)在构造函数中赋值是执行赋值过程,数据成员在执行函数体之前就已经创建,有一个值,在函数体里重新赋给数据成员新的值。
2.4 初始化列表的知识要点
(1)const成员,引用型成员要求在创建时就必须赋初值,因此只能使用初始化列表对其进行初始化。
(2)初始化列表仅指定用于初始化成员的值,并不指定这些成员初始化执行的次序。成员被初始化的次序就是类中定义成员的次序。
(3)初始化式,即括号里的内容可以是变量,是常量也可以是表达式。
3 默认构造函数
3.1 如何定义一个调用默认构造函数的类对象
当定义一个类对象时没有提供初始化式,系统会调用默认构造函数。
Student stud1;
Student stud1(); 错误
这时编译器将类对象stud1的定义理解为一个函数的声明,该函数不接受参数并返回一个Student类型的对象。
3.2 合成的默认构造函数
(1)如果类中没有定义任何一个构造函数,编译器会自动合成一个默认构造函数。
class Student
{
int m_age;
char m_sex;
string m_name;//自动运行标准库string类的默认构造函数
};
内置类型的数据成员,只有定义在全局作用域中的对象才初始化。
类类型的数据成员,自动运行其默认构造函数进行初始化。
(2)若定义了不带参数或参数均有默认值得构造函数,C++编译器都认为其是默认构造函数。
Student(void)
{
m_age = 0;
m_sex = 'm';
};
Student(int age=0, char sex='m', string name="")
{
m_age = age;
m_sex = sex;
m_name = name;
};
(3)一个类只能有一个默认构造函数。
(4)一个类通常应该显式的定义一个默认构造函数。
4 复制构造函数
4.1 复制构造函数的定义
复制构造函数(拷贝构造函数)是一种特殊的构造函数,具有单个形参,该形参是该类类型的引用。
把新创建的类对象初始化为形参类对象的副本。
可以用下列方式进行定义:
Student::Student(const Student& stu)
{
...
}
4.2 如何调用复制构造函数
复制构造函数也是编译系统自动调用的,下列情况会调用复制构造函数。
(1)根据另一同类型的对象初始化一个对象
类名 对象2(对象1);
类名 对象2 = 对象1;
Student stud2(stud1); //直接初始化
Student stud2 = stud1; //复制初始化
(2)当类对象作为实参时,传递给函数的非引用类型形参时。
(3) 返回值为非引用类类型时。
4.3 合成的默认复制构造函数
(1) 如果类中没有显式的定义复制构造函数,编译器会合成一个默认的复制构造函数
(2)这个默认的复制构造函数执行的行为是,逐个成员初始化,将新对象初始化为原对象的副本。
class Student{
int m_age;
string m_name;
char m_sex;
};
默认的复制构造函数执行的操作如下:
Student(const Student &orig):m_age(orig.m_age),m_name(orig.m_name),m_sex(orig.m_sex){}
4.4 复制构造函数知识要点
(1)当类中有成员是指针变量时或者在类中有动态内存的情况发生,此时对于复制对象时发生的情况要加以控制,要求必须显式的定义自己的复制构造函数,否则在复制过程中容易产生错误。
(2)最好在类中显式的定义复制构造函数,而不是使用默认的复制构造函数。
5 关于构造函数的总结
(1)构造函数的名字与类的名字相同。
(2)构造函数不返回值,没有任何类型。
(3)构造函数可以重载,根据实参来确定调用哪个构造函数。
(4)当类对象创建时,构造函数会自动地执行。
(5)构造函数的初始化列表是执行初始化过程,在创建数据成员时同时给它们赋初值。
(6)如果类中没有显式的定义任何一个构造函数,编译器会自动合成一个默认构造函数。
(7)不带参数或各参数均有默认值得构造函数,都称为默认构造函数。
(8)一个类只能有一个默认构造函数。
(9)如果类中没有显式定义复制构造函数,编译器会合成一个默认的复制构造函数。
(10)当类的成员有指针或者类中有动态内存分配,必须自己定义复制构造函数。
(11)类应该显式定义复制构造函数和默认构造函数。
内容回顾
(1)构造函数可以重载吗?
(2)默认构造函数有哪三种形式?
(3)初始化和赋值的区别是什么?
(4)什么样的数据成员要求必须使用初始化列表?
(5)什么情况下必须自己定义复制构造函数?
6 析构函数
6.1 析构函数的定义
同构造函数一样,析构函数也是类的特殊成员函数,在撤销类对象时会自动调。
在对象销毁前执行用户希望执行的操作(如释放动态申请的资源)。
6.2 合成的析构函数
不管是否显式的定义了析构函数,编译器都会自动的合成一个析构函数。
合成的析构函数按照数据成员创建时相反的顺序来释放这些成员。
对于类类型的数据成员,合成析构函数调用该类的析构函数来撤销此成员。
6.3 析构函数知识要点
(1)析构函数的名字是“~”加上类的名字(中间没有空格)。
(2)析构函数在类对象销毁时自动执行。
(3)与构造函数一样,析构函数也没有任何类型,即不属于返回值函数也不属于void函数。
(4)与构造函数不同,析构函数没有参数。
(5)一个类只能有一个自定义的析构函数,析构函数不能重载。
(6)不管是否显式的定义了析构函数,编译器都会自动的合成一个析构函数。
7 对象的动态建立与释放
7.1 new操作符
动态创建对象
int *pi = new int;
string *ps1 = new string;
string *ps2 = new string("hello world");
返回指向新创建对象的指针,可以通过指针来访问该对象。
string *ps3 = new string[3];
为含有三个string对象的数组分配空间,返回指向数组第一个元素的指针。
7.2 delete操作符
释放动态创建的对象。
如果是单个对象空间,使用下列方式:
delete pt;
如果是数组空间,使用下列方式:
delete [ ] pt2;
表示把整个数组的空间都释放,而不是数组的某个元素。
7.3 知识要点
(1)在delete之后,要设置指针的值为NULL,避免野指针。
(2)C++允许删除值为NULL的指针。
int *p = NULL;
delete p;
(3)需要注意动态内存相关的:内存泄漏,越界,重复释放等问题。
7.4 new delete与malloc free的区别
(1)用malloc函数需要制定内存分配的字节数,不能初始化对象。
(2)new会自动调用类对象的构造函数,对类进行初始化。
(3)new是C++运算符,返回指向创建对象的指针,malloc是c标准库函数,返回void*。
(4)malloc失败会返回NULL,new失败会抛出异常,程序自动终止。
(5)delete会自动调用类对象的析构函数,free不会调用类对象的析构函数。
#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
class Cperson
{
private:
char* m_p;
public:
Cperson(const char* pname = "")
{
m_p = new char[strlen(pname)+1]; //注意strlen的使用条件,不能strlen(NULL),段错误
if(NULL!=m_p)
{
strcpy(m_p,pname);
}
cout<<"Cperson constructor"<<endl;
}
Cperson(const Cperson& otherPerson)
{
m_p = new char[strlen(otherPerson.m_p)+1];
strcpy(m_p,otherPerson.m_p);
cout<<"Copy"<<endl;
}
~Cperson()
{
if(NULL!=m_p)
{
delete []m_p; //注意删除是整个数组空间
}
}
};
int main()
{
Cperson c1("lirong");
return 0;
}
8 构造函数和析构函数执行顺序
8.1 调用顺序说明
在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反。
内容回顾
1)析构函数可以重载吗? 不可以
2)如果定义了析构函数,编译器是否合成析构函数 是
3)何时需要定义析构函数 构造函数中有new
4)new delete和malloc free的区别 五点
5)如何释放数组对象 free []array;