1. 构造函数和析构函数
1.1 构造函数
构造函数是一种特殊的函数,用来在对象实例化的时候初始化对象的成员变量。在创建(实例化)一个新对象的时候一定会调用!!!
- 函数名和类名相同,没有返回类型
- 构造函数在创建对象时,自动被调用,而不能像普通成员函数一样直接去调用
功能:专门用于对象的初始化工作,在类的对象创建时定义初始状态
特点:
- 构造函数的名字和类名是相同的
- 构造函数是没有返回值类型的,也不能写
void。可以有形参(也可以重载) - 在创建对象的时候,会自动调用。而且是一定会调用,但是只会调用一次,不能通过已有对象手动调用构造函数。
- 如果一个类中没有显示声明的构造函数,那么编译器会自动生成一个无参构造函数,自动生成的构造函数,函数体为空且没有参数。
- 但是如果程序员写了至少一个构造函数,那么编译器就不会生成了。
- 在存在多个构造函数的情况下,编译器会在调用构造函数的时候,根据构造函数的参数自动匹配相应的构造函数进行调用。
构造函数格式:
class 类名
{
类名(构造形参表)
{
//主要负责对象的初始化,即初始化成员变量
}
};以学生类为例:
#include<iostream>
using namespace std;
class Student
{
public:
/*void */Student(const string name, const int age, const int id) //构造函数不能有返回值
{
cout << "构造函数" << endl;
m_name = name;
m_age = age;
m_id = id;
}
void who(void)
{
cout << "我叫" << m_name << ", 今年" << m_age
<< "岁, 学号是" << m_id << endl;
}
private:
string m_name;
int m_age;
int m_id;
};
int main()
{
//创建对象,构造函数将自动被调用
//(...): 指明构造函数需要的实参
Student s("贾修", 18, 1);
//Student s = Student("贾修", 18, 1);//与上面的写法相同
s.who();
//Error: 构造函数不能显示调用
//s.student("贾修", 18, 1);
return 0;
}练习:实现一个电子时钟类,要求使用构造函数初始化当前的时间,以秒为单位运行
#include<iostream>
#include<string>
#include<windows.h>
using namespace std;
class Clock
{
public:
Clock(const time_t t)
{
//C 库函数 time.h
//struct tm* localtime(const time_t * timer)
tm* local = localtime(&t);
m_hour = local->tm_hour;
m_min = local->tm_min;
m_sec = local->tm_sec;
}
void run(void)
{
while (1)
{
//打印当前时间
//计时+1秒:
cout << m_hour << ":" << m_min << ":" << m_sec << endl;
m_sec++;
if (m_sec == 60)
{
m_min++;
m_sec = 0;
}
if (m_min == 60)
{
m_hour++;
m_min = 0;
}
if (m_hour == 24)
{
m_hour = 0;
}
//休眠1s windows下头文件windows.h
Sleep(1000);
//清屏
system("cls");
}
}
private:
int m_hour;
int m_min;
int m_sec;
};
int main()
{
Clock c(time(NULL));
c.run();
return 0;
}1.1.1 对象的创建
- 在栈区创建单个对象
类名 对象(构造参数);
类名 对象 = 类名(构造参数); //和上面相同#include<iostream>
using namespace std;
class Student
{
public:
Student(const string name, const int age, const int id)
{
cout << "构造函数" << endl;
m_name = name;
m_age = age;
m_id = id;
}
void who(void)
{
cout << "我叫" << m_name << ",今年" << m_age
<< "岁,学号是" << m_id << endl;
}
private:
string m_name;
int m_age;
int m_id;
};
int main()
{
Student s = Student("蔡徐坤", 18, 10001);
//Student s("蔡徐坤", 18, 10001);
s.who();
return 0;
}- 在栈区创建多个对象(对象数组)
类名 对象数组[元素个数] = {类名(构造实参表),...}//在栈区创建多个对象
Student sarr[3] =
{
Student("古丽娜扎",20,10002),
Student("迪丽热巴",22,10003),
Student("马尔扎哈",25,10004)
};
sarr[0].who();
sarr[1].who();
sarr[2].who();- 在堆区创建单个对象
创建:
类名 *对象指针 = new 类名(构造实参表);销毁:
delete 对象指针;//在堆区创建单个对象
Student *ps = new Student("林黛玉",19,10005);
ps->who();//(*ps).who() 间接
delete ps;
ps = NULL;- 在堆区创建对象数组
创建:
类名 *对象指针 = new 类名[元素个数]{类名(构造实参表),...};销毁:
delete[] 对象指针;//在堆区创建多个对象
Student* parr = new Student[3]
{
Student("潘金莲",30,10006),
Student("唐三娘",35,10007),
Student("孙三娘",31,10008)
};
parr[0].who();//(parr + 0)->who()
parr[1].who();//(parr + 1)->who()
parr[2].who();//(parr + 2)->who()
delete[] parr;
parr = NULL;- malloc和new的区别:
malloc申请的内存区域返回类型是void*类型,需强制转换为对应类型才可使用;而new则是自动生成所需要的类型。malloc申请内存需要手动计算大小,而new则自动计算相应的大小cppint *p = (int *)malloc(sizeof(int)); int *p = new int;new会调用类构造函数为对象初始化,而malloc不会。
#include<iostream>
using namespace std;
class Student
{
public:
Student(const string name, const int age, const int id)
{
cout << "构造函数" << endl;
m_name = name;
m_age = age;
m_id = id;
}
void who(void)
{
cout << "我叫" << m_name << ",今年" << m_age
<< "岁,学号是" << m_id << endl;
}
private:
string m_name;
int m_age;
int m_id;
};
int main()
{
Student* ps = new Student("林黛玉",19, 10005);
ps->who();//(*ps).who() 间接
Student* ps1 = (Student*)malloc(sizeof(Student));//不要这样写代码
ps1->who();
free(ps1);
ps1 = NULL;
delete ps;
ps = NULL;
return 0;
}explicit关键字用于修饰构造函数(一般修饰只有一个参数的构造函数,如果有多个参数,其他参数必须有默认值),它的作用是表示该构造函数的调用必须是“显示的”(明确调用),防止构造函数隐式转换。默认的情况下类的构造函数默认是隐式的,即构造函数可以被隐式调用。
#include <iostream>
class Int
{
public:
/*explicit*/ Int(int a)
{
std::cout << "构造函数" << std::endl;
m_a = a;
}
private:
int m_a;
};
int main()
{
Int a = 10; // 隐式转换
return 0;
}1.1.2 构造函数参数的缺省
C++中的函数参数支持缺省,即给定默认值。构造函数和普通函数类似,也可以带有缺省参数。可防止出现脏数据。
#include<iostream>
using namespace std;
class Student
{
public:
Student(const string name, const int age = 0, const int id = 0)
{
m_name = name;
m_age = age;
m_id = id;
}
void who(void)
{
cout << "我叫" << m_name << ", 今年" << m_age
<< "岁, 学号是" << m_id << endl;
}
private:
string m_name;
int m_age;
int m_id;
};
int main()
{
Student s("abc", 18, 10001);
s.who();
Student s2("def");
s2.who();
return 0;
}1.1.3 构造函数的重载
C++中的函数参数支持重载,即函数名相同,但是参数列表不同,与返回值无关。构造函数和普通函数类似也可以重载。
#include<iostream>
using namespace std;
class Student
{
public:
Student(const string name, const int age, const int id)
{
cout << "构造函数1" << endl;
m_name = name;
m_age = age;
m_id = id;
}
Student(const string name, const int age)
{
cout << "构造函数2" << endl;
m_name = name;
m_age = age;
}
Student(const string name)
{
cout << "构造函数3" << endl;
m_name = name;
}
void who(void)
{
cout << "我叫" << m_name << ", 今年" << m_age
<< "岁, 学号是" << m_id << endl;
}
private:
string m_name = "qq";
int m_age = 0;
int m_id = 0;
};
int main()
{
Student s1("abc", 18, 10001);
s1.who();
Student s2("def", 19);
s2.who();
Student s3("hij");
s3.who();
return 0;
}1.1.4 缺省(默认)构造函数
类中可以包含其他类类型的成员变量:
class A
{
public:
int m_i;
};
class B
{
public:
int m_j; //基本类型
A m_a; //类类型(成员子对象)
string str; //类类型
};如果类中没有定义任何构造函数(无参构造函数、含参构造函数),编译器会为该类提供一个缺省(无参构造函数):
- 对于基本类型的成员变量不能初始化
- 对于类类型的成员变量,会自动调用相应的无参构造函数来初始化
#include<iostream>
using namespace std;
class A
{
public:
A(/*int num*/void)
{
cout << "A:A(void)" << endl;
m_i = 1234;
}
int m_i;
};
class B
{
public:
//编译器会为B类提供类似下面的缺省构造函数
//B(void)行
int m_j; //基本类型
A m_a; //类类型(成员子对象)
string str;
};
int main()
{
B b;
cout << b.m_j << endl; //未知
cout << b.m_a.m_i << endl; //1234
if (b.str.size())
{
printf("NOT NULL\n");
}
else
{
printf("NULL\n");
}
return 0;
}如果类中自己定义了构造函数,无论是否有参数,编译器都不会再提供缺省的无参构造函数了:
#include<iostream>
using namespace std;
class A
{
public:
A(int num) //已经定义了构造函数。编译器都不会再提供缺省的无参构造函数了
{
cout << "A::A(void)" << endl;
m_i = 1234;
}
int m_i;
};
class B
{
public:
int m_j; //基本类型
A m_a; //类类型(成员子对象)
string str;
};
int main()
{
B b; // 错误:A类没有无参构造函数
return 0;
}1.2 初始化表
除了使用构造函数来初始化类内的成员外,还有一种方式——初始化表:
class 类名
{
类名(形参表):成员变量(初值),...{//构造函数体}
};初始化表方式来初始化类内成员:
#include<iostream>
using namespace std;
class Student
{
public:
//在定义成员变量时,同时初始化
Student(const string name, const int age, const int no)
:m_name(name), m_age(age), m_no(no) {}
void who(void)
{
cout << m_name << "," << m_age << "," << m_no << endl;
}
private:
string m_name;
int m_age;
int m_no;
};
int main()
{
Student s("abc", 18, 001);
s.who();
return 0;
}多数情况下使用初始化表和在构造函数体中赋初值没有太大区别,可以任选一种方式,但是在某些特殊情况下必须要使用初始化表:
- 如果有类类型的成员变量,而该类又没有无参构造函数,则必须通过初始化表来初始化该变量
#include<iostream>
using namespace std;
class A
{
public:
A(const int data)
{
cout << "A的构造函数" << endl;
m_data = data;
}
int m_data;
};
class B
{
public:
B(void) :m_a(1234)
{
cout << "B的构造函数" << endl;
}
A m_a; //类类型成员变量
};
int main()
{
B b;
cout << b.m_a.m_data << endl; //1234
return 0;
}- 如果类中包含
const或引用型的成员变量,必须要使用初始化表来初始化
#include<iostream>
using namespace std;
int num = 100;
class A
{
public:
A(void) :m_c(200), m_r(num)
{
// m_r = num; // 错误:引用必须在初始化时绑定
}
const int m_c;
int& m_r;
};
int main()
{
A a;
cout << a.m_c << "," << a.m_r << endl;
return 0;
}练习:修改电子时钟类,要求使用初始化表
#include<iostream>
#include<ctime>
#include<windows.h>
using namespace std;
class Clock {
public:
Clock(tm* local) : m_hour(local->tm_hour), m_min(local->tm_min),
m_sec(local->tm_sec)
{
}
void run(void)
{
while (1)
{
//打印当前时间
cout << m_hour << ":" << m_min << ":" << m_sec << endl;
//计时+1秒:
m_sec++;
if (m_sec == 60)
{
m_min++;
m_sec = 0;
}
if (m_min == 60)
{
m_hour++;
m_min = 0;
}
if (m_hour == 24)
{
m_hour = 0;
}
//休眠1s windows下头文件windows.h
Sleep(1000);
//清屏
system("cls");
}
}
private:
int m_hour;
int m_min;
int m_sec;
};
int main()
{
time_t t = time(NULL);
tm* local = localtime(&t);
Clock c(local);
c.run();
return 0;
}注意:成员变量的初始化顺序是由声明顺序决定,而与初始化表的顺序无关。不要用一个成员变量去初始化另一个成员变量。
#include<iostream>
using namespace std;
class A
{
public:
//A(int a, int b, int c) : m_a(a), m_b(b), m_c(c)
A(int a, int b, int c) : m_a(a), m_c(m_a), m_b(m_c)
//如果是按m_a->m_c->m_b顺序, m_a = 1, m_c = 1, m_b = 1
//如果是按m_a->m_b->m_c顺序, m_a = 1, m_b = 随机值, m_c = 1 //按声明顺序
{
}
public:
int m_a;
int m_b;
int m_c;
};
int main()
{
A a(1, 2, 3);
cout << a.m_a << ", " << a.m_b << ", " << a.m_c << endl;
return 0;
}当类的声明和定义分开时,初始化表应该写在定义的地方(类似于函数的):
#include<iostream>
using namespace std;
class A
{
public:
A(int a, int b, int c) /* : m_a(a), m_b(b), m_c(c)*/;
void print();
private:
int m_a;
int m_b;
int m_c;
};
A::A(int a, int b, int c) : m_a(a), m_b(b), m_c(c)
{
}
void A::print()
{
cout << m_a << "," << m_b << ", " << m_c << endl;
}
int main()
{
A a(1, 2, 3);
a.print();
return 0;
}1.3 析构函数
与构造函数相对应,构造函数是对象创建的时候自动调用的,而析构函数就是对象在销毁的时候自动调用的。与构造函数作用刚好相反,构造函数是用来初始化成员变量,析构函数是用来释放对象占用的资源。
- 函数名必须是
~类名 - 没有返回类型,也没有参数,也无法重载
class 类名
{
~类名(void)
{
//负责清理对象创建时的动态资源
}
};当对象销毁时,该类的析构函数会自动被执行(不建议手动调用析构):
#include<iostream>
using namespace std;
class A
{
public:
A(const int data)
{
cout << "A的构造函数" << endl;
m_data = new int(data);
}
~A()
{
cout << "A的析构函数" << endl;
delete m_data;
m_data = NULL;
}
private:
int *m_data;
};
int main()
{
A a(10);
return 0;
}- 栈对象:当离开作用域时,其析构函数被自动调用
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "构造函数" << endl;
}
~A()
{
cout << "析构函数" << endl;
}
};
void fun()
{
cout << "fun()" << endl;
A a;
}
int main()
{
cout << "调用fun()之前" << endl;
fun();
cout << "调用fun()之后" << endl;
return 0;
}- 堆对象:堆对象的析构函数被
delete操作符调用
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "构造函数" << endl;
}
~A()
{
cout << "析构函数" << endl;
}
};
int main()
{
A* a = new A();
cout << "delete之前" << endl;
delete a; //堆区对象需要手动调用delete操作符会调用析构函数
cout << "delete之后" << endl;
return 0;
}- 如果一个类没有显式定义析构函数,那么系统会为该类提供一个缺省的析构函数:
- 对于基本类型的成员变量,什么也不做
- 对于类类型的成员变量,调用相应类的析构
#include<iostream>
using namespace std;
class A
{
public:
A(void)
{
cout << "A::A()" << endl;
}
~A(void)
{
cout << "A::~A()" << endl;
}
};
class B
{
public:
A m_a; //类类型的成员变量
};
int main()
{
B b;
return 0;
}- 对象创建和销毁的过程
- 创建过程:
- 分配内存
- 构造成员子对象(按声明顺序)
- 执行构造函数代码
- 销毁过程:
- 执行析构函数代码
- 创建过程:
#include<iostream>
using namespace std;
class A
{
public:
A(void)
{
cout << "A::A()" << endl;
}
~A(void)
{
cout << "A::~A()" << endl;
}
};
class B
{
public:
B(void)
{
cout << "B::B()" << endl;
}
~B(void)
{
cout << "B::~B()" << endl;
}
};
class C
{
public:
C(void)
{
cout << "C::C()" << endl;
}
~C(void)
{
cout << "C::~C()" << endl;
}
public:
A m_a; //类类型的成员变量
B m_b; //类类型的成员变量
};
int main()
{
C c;
return 0;
}1.4 this指针
定义:类中的内部都隐藏一个该类类型的指针参数,名为 this
- 对于普通的成员函数,
this指向调用该函数的对象 - 对于构造函数,
this指向正在创建的对象
this 指针是隐式传递给每个非静态成员函数的,它允许成员函数访问对象的成员变量和调用其他成员函数。this 指针的类型是指向类类型的指针,其类型是类类型名加上一个星号(*)。例如,如果类名为 Student,那么 this 指针的类型就是 Student*。this 指针在成员函数内部是隐式可用的,你不需要显式地声明或传递它。你可以在成员函数中通过 this 指针来访问对象的成员变量和其他成员函数。
#include<iostream>
using namespace std;
class Student
{
public:
Student(const string& name, int age)
:m_name(name), m_age(age)
{
cout << "构造函数" << this << endl;
this->m_age = 18; // 显式使用this指针
}
void who(void)
{
cout << m_name << ", "<< m_age << endl;
cout << this->m_name << ", "<< this->m_age << endl; // 显式使用this指针
}
private:
string m_name;
int m_age;
};
int main()
{
Student stu1("蔡徐坤", 25);
cout << "&stu1 = " << &stu1 << endl;
Student stu2("鹿晗", 26);
cout << "&stu2 = " << &stu2 << endl;
stu1.who(); // 相当于 Student::who(&stu1);
stu2.who(); // 相当于 Student::who(&stu2);
return 0;
}需要显式使用 this 指针的场景:
- 区分作用域
- 返回调用对象的自身
- 从类的内部销毁对象自身
#include<iostream>
using namespace std;
class Counter
{
public:
Counter(int num = 0) //区分作用域
{
this->num = num;
}
Counter& add(void) //返回调用对象本身
{
++num;
cout << "num = "<< num << endl;
//this指向调用对象
//*this就是调用对象自身
return *this; //返回自引用
}
void destroy(void) //从类的内部销毁对象自身
{
cout << "this = "<< this << endl;
delete this;
}
int num;
};
int main()
{
Counter cn(1);
cn.add().add().add(); // 链式调用
cout << cn.num << endl; //4
Counter* pc = new Counter(123);
cout << pc->num << endl; //123
cout << "pc = "<< pc << endl;
pc->destroy(); // 对象自我销毁
//delete pc; // 已经释放过了,不需要再释放
return 0;
}