构造函数
构造函数是与同类名的特殊成员函数,主要用来初始化对象的数据成员。
构造函数的特点:
- 与类同名
- 没有返回类型
- 可以被重载
- 由系统自动调用,不允许在程序中显示调用。
#include <iostream>
using namespace std;class student{
private:string m_name;int m_age;int m_num;public:student(const string &name, int age, int num) {m_name = name;m_age = age;m_num = num;}void Cout() {cout << "my name is " << m_name << "," << "my age is " << m_age << endl;}};int main(int argc, const char *argv[])
{student s("xiaoming", 18, 1234);s.Cout();return 0;
}
缺省构造函数
缺省构造函数也称无参构造函数,但其未必真的没有任何参数,为一个有参构造函数的每个参数都提供一个缺省值,同样可以达到无参构造函数的效果。
注意:
- 如果一个类没有定义任何构造函数,那么编译器会为其提供一个缺省构造函数
- 对基本类型的成员变量,不做初始化
- 对类类型的成员变量(成员子对象),将自动调用相应类的缺省构造函数来初始化。
#include <iostream>
using namespace std;class A{public:A(void) {cout << "这是A的构造函数" << endl;m_t = 0;}
public:int m_t;
};class B{
public:int m_j;//基本类型成员变量A m_a;//类类型成员变量(成员子对象)};
int main(int argc, const char *argv[])
{B b;//调用成员对象m_a的无参构造函数 调用B的缺省构造函数cout << b.m_j << endl;cout << b.m_a.m_t << endl;return 0;
}
构造函数的重载
#include <iostream>
using namespace std;class Desk{
private:int length, weight, hight, wide;
public:Desk(int l, int w, int h, int ww);void put() {cout << length << weight << hight << wide << endl;}Desk() {cout << "Desk(void)" << endl;length = 0;weight = 0;hight = 0;wide = 0;}
};Desk::Desk(int l, int w, int h, int ww) {cout << "construct" << endl;length = l;weight = w;hight = h;wide = ww;
}
int main(int argc, const char *argv[])
{Desk s(1,2,3,4);s.put();Desk();return 0;
}
类型转换构造函数
将其他类型转换为当前类类型需要借助转换构造函数,转换构造函数只有一个参数。
#include <iostream>
#include <cstring>using namespace std;class test {private:int m_i;string m_c;public:test(void) {cout << "test(void)" <<endl;m_i = 0;}explicit test(int n) {cout << "test(int)" << endl;//类型转换构造函数m_i = n;}explicit test(const char *str) {cout << "test(char *)" << endl;m_i = strlen(str);m_c = str;cout << m_c << endl;}void printf() {cout << m_i << endl;}};
int main(int argc, const char *argv[])
{test a1;a1.printf();test a = test(2);//编译器会找参数类型为int的构造参数a.printf(); test b = test("abc");//编译器会找参数类型为char *的构造参数b.printf();return 0;
}
explicit关键字,就是告诉编译器需要类型转换时,强制要求写成如下形式:
test a = test(2);
test a = 2;//会报错
拷贝构造函数
用一个已定义的对象构造同类型的副本对象,将调用该类的拷贝构造构造函数
class A{A(const A &that){//拷贝构造函数,注意参数必须是常引用...}
};A a;A b(a);//调用拷贝构造A c = a;//调用拷贝构造
#include <iostream>
using namespace std;class A{public:int m_t;A(int date = 0) {cout << "A(int)" << endl;m_t = date;}A(const A& that) {//拷贝构造函数cout << "A(const A&)" << endl;m_t = that.m_t;}
};
int main(int argc, const char *argv[])
{A a;A b(a);//编译器会调用拷贝构造函数A c = a;//调用拷贝构造函数return 0;
}
- 如果一个类没有显式定义拷贝构造函数,那么编译器会为其提供一个缺省拷贝构造函数
- 对基本类型成员变量,按字节复制
- 对类类型成员(成员子对象),调用相应类型的拷贝构造函数
class User {string m_name;//调用string类的拷贝构造函数int m_age;//按字节复制
};
#include <iostream>
using namespace std;class A{public:int m_t;A(int date = 0) {cout << "A(int)" << endl;m_t = date;}A(const A& that) {//拷贝构造函数cout << "A(const A&)" << endl;m_t = that.m_t;}
};class B{
public:A m_a;
};int main(int argc, const char *argv[])
{
#if 0A a;A b(a);//编译器会调用拷贝构造函数A c = a;//调用拷贝构造函数B d;
#endifB b;b.m_a.m_t = 98;B b1;b1 = b;cout << b1.m_a.m_t << endl;//调用B的缺拷贝构造函数,由于有成员对象m_t,所以会调用A的拷贝构造函数return 0;
}
初始化列表
构造函数的初始化列表
构造函数对数据成员进行初始化还可以通过成员初始化列表的方式完成。语法格式为:
构造函数名(参数表):成员1(初始值参数),成员2(初始值参数){
}
例如:
#include <iostream>
using namespace std;class Desk{
private:int length, weight, hight, wide;
public:Desk(int l, int w, int h, int ww);void put() {cout << length << weight << hight << wide << endl;}Desk() {cout << "Desk(void)" << endl;}
};Desk::Desk(int l, int w, int h, int ww) :length(l), weight(w), hight(h), wide(ww){cout << "construct" << endl;length = l;weight = w;hight = h;wide = ww;
}
int main(int argc, const char *argv[])
{Desk s(1,2,3,4);s.put();Desk s2;return 0;
}
需要显式初始化列表的场景
一般而言,使用初始化列表和在构造函数体对成员变量进行赋初值,两者区别不大,可以人员一种,但是下面几种场景必须使用初始化列表:
- 如果有类类型的成员变量(成员子对象),而该类有没有无参构造函数,则必须要通过初始化列表显式指明其初始化方式。
#include <iostream>
using namespace std;class A{
private:int date_t;
public:A(int date) {date_t = date;cout << "A(int)" << endl;}
};class B {
private:int m_t;
public:B(void) :m_t(123){cout << "B(void)" << endl;cout << m_t << endl;}
};
int main(int argc, const char *argv[])
{B b;//一定会去构造成员对象m_t,未指定如何构造,系统去调用m_t的无参构造函数return 0;
}
- 被const修饰的成员变量(常成员变量)必须要在初始化列表中初始化参数
- 引用型成员变量必须要在初始化列表中初始化
#include <iostream>
using namespace std;int num = 100;
class A{
public:int & m_t;const int m_c;
/** error此种写法错误,无法const类型和引用类型无法这样初始化A(void) {m_t = num;m_c = 100;}
*/A(void):m_t(num),m_c(num) {cout << m_t << " " << m_c << endl;}
};int main(int argc, const char *argv[])
{A a;return 0;
}
初始化顺序
类中成员变量按声明顺序依次被初始化,而与初始化中的顺序无关。
#include <iostream>
using namespace std;class A{
public:A(int a) {cout << "A(construct)" << endl;}
};class B{
public:B(int b) {cout << "B(construct)" << endl;}
};class C{
private:A m_a;B m_b;
public:C(int a, int b): m_b(b), m_a(a) {}
};int main(int argc, const char *argv[])
{C c(2,3);return 0;
}
this指针
什么是this指针
#include <iostream>
using namespace std;class stu{
private:int age;string name;public:stu(int m_age, string m_name) {//编译完成之后就是stu(stu *this, int m_age, string m_name);age = m_age;name = m_name;}void cprintf() {cout << name << ":" << age << endl;}
};
int main(int argc, const char *argv[])
{stu zs(18,"zhangsan");//传递参数为(&zs,18,"zhangsan");stu ls(21,"lisi");//传递参数为(&ls, 21, "lisi");zs.cprintf();ls.cprintf();return 0;
}
this是一个用于标识对象自身的隐式指针,代表对象自身的地址。
在编译类成员函数时,C++编译器会自动将this指针添加到成员函数的参数表中。在用类的成员函数时,调用对象会把自己的地址通过this指针传递给成员函数。
this指针有什么用
需要显式使用this指针的常见场景:
- 类中的成员变量和参数变量名字一样,可以通过this指针区分
- 从成员函数中返回调用对象自身(返回自引用),支持链式调用
- 在成员函数中销毁对象自身(对象自销毁)
#include <iostream>
using namespace std;class Count{private:int count;public:Count(int count = 0) {this->count = count;}Count &add(void) {++count;return *this;}void printf() {cout << count << endl;}void destroy() {cout << "this:" << this << endl;delete this;}
};int main(int argc, const char *argv[])
{Count cnt;cnt.printf();cnt.add().add().add();cnt.printf();Count *pnt = new Count;pnt->printf();pnt->add();pnt->printf();pnt->destroy();cout << pnt << endl;return 0;
}
常成员函数
在C++中,为了禁止成员函数修改成员数据的值,可以将它设置为常成员函数。设置方法就是在函数体之前加上const关键字。
class X{void func(参数1, 参数2, ...) const {}
};
#include <iostream>
using namespace std;class A {
private:int num,age;string name;
public:A(int num, const string &name, int age) {this->age = age;this->num = num;this->name = name;}void printf(void) const{cout << "i am " << name << "i am age is " << age << "num:" << num << endl;}};
int main(int argc, const char *argv[])
{A a(8499, "zhangsan", 21);a.printf();return 0;
}
常函数的使用注意事项:
- 常对象只能调用常函数,非常对象既可以调用非常函数,也可以调用常函数
- 函数名和形参表相同的成员函数,常版本和非常版本可以构成重载
- 常对象只能选择常版本
- 非常对象优先选择非常版本
- 被mutable修饰的成员可以常函数中修改(了解)
#include <iostream>
using namespace std;class A{
private:int n,m;
public:A(int nn = 1, int mm = 2):n(nn),m(mm) {};void func(void) {cout << "this is 11" << endl;}void bar(void) const{cout << "this is 15"<< endl;}void func(void) const{cout << "this is 19" << endl;}
};
int main(int argc, const char *argv[])
{A a;a.func();a.bar();const A b;b.func();return 0;
}
析构函数
析构函数是与类同名的另一个特殊成员函数,作用与构造函数相反,用于对象生存期结束时,完成对象的清理工作。析构函数的名称是:~类名
class X{
public:X() {//构造函数}~X() {//析构函数}
};
析构函数的特点:
- 无参无返回值
- 不能重载
- 只能由系统调用,不能显示调用
- 栈对象,离开其作用域时析构函数自动调用
- 堆对象,执行delete操作时析构函数自动调用
#include <iostream>
using namespace std;class Integer{
private:int *m;public:Integer(int i = 0) {m = new int(i);}~Integer(void) {cout << "析构函数" << endl;delete m;}void printf(void) {cout << *m << endl;}
};
int main(int argc, const char *argv[])
{if(1) {Integer a(100);a.printf();cout << "test1" << endl;Integer *pi = new Integer(200);pi->printf();delete pi;cout << "test2" << endl;}cout << "test3" << endl;return 0;
}
缺省析构函数
如果类没有显式定义析构函数,那么编译器会为其提供一个缺省析构函数,缺省析构的函数体为空,在析构函数体执行完毕后,类中的成员会被自动销毁。
- 对基本类成员变量,什么也不做
- 对类类成员变量(成员子对象),将会自动调用相应类的析构函数。
#include <iostream>
using namespace std;class A{
public:A(void) {cout << "A(void)" << endl;}~A(void) {cout << "~A(void)" << endl;}};class B{
public:B(void) {cout << "B(void)" << endl;}~B(void) {cout << "~B(void)" << endl;}A a;};int main(int argc, const char *argv[])
{B b;return 0;
}
对象的穿件和销毁的过程
对象创建
- 分配内存
- 构造成员对象
- 调用构造函数
对象销毁
- 调用析构函数
- 析构成员对象
- 释放内存