|
1、构造函数和析构函数
构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。
- 构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
1 构造函数
- 没有返回值 不用写void
- 函数名 与 类名相同
- 可以有参数 ,可以发生重载
- 构造函数 由编译器自动调用一次 无须手动调用
2 析构函数
- 没有返回值 不用写void
- 函数名 与类名相同 ,函数名前 加 ~
- 不可以有参数 ,不可以发生重载
- 析构函数 也是由编译器自动调用一次,无须手动调用
2、构造函数的分类和调用
1 分类
- 按照参数分类: 有参 无参(默认)
- 按照类型分类: 普通 拷贝构造 ( const Person & p )
2 调用
1 调用方法:
2 注意事项
- 不要用括号法 调用无参构造函数 Person p3(); 编译器认为代码是函数的声明
- 不要用拷贝构造函数 初始化 匿名对象 Person(p3); 编译器认为 Person p3对象实例化 如果已经有p3 p3就重定义
3 匿名对象 特点: 当前行执行完后 立即释放
class Person
{
public:
Person()
{
cout << &#34;Person的默认构造函数调用&#34; << endl;
}
Person(int age)
{
m_Age = age;
cout << &#34;Person的有参构造函数调用&#34; << endl;
}
//拷贝构造函数
Person(const Person& p)
{
cout << &#34;Person的拷贝构造函数调用&#34; << endl;
m_Age = p.m_Age;
}
//析构函数
~Person()
{
cout << &#34;Person的析构函数调用&#34; << endl;
}
int m_Age;
};
//构造函数的调用
void test01()
{
//Person p;
//1、括号法
//Person p1(10);
//Person p2(p);
//注意事项一
//不要用括号法 调用无参构造函数 Person p3(); 编译器认为代码是函数的声明
//2、显示法
//Person p3 = Person(10); //有参构造
//Person p4 = Person(p3); //拷贝构造
//Person(10); //匿名对象 特点: 当前行执行完后 立即释放
//注意事项二
//不要用拷贝构造函数 初始化 匿名对象 Person(p3); 编译器认为 Person p3对象实例化 如果已经有p3 p3就重定义
//3、隐式法
Person p5 = 10; //Person p5 = Person(10);
Person p6 = p5;
}
3、拷贝构造函数的调用时机
- 用已经创建好的对象来初始化新的对象
- 值传递的方式 给函数参数传值
- 以值方式 返回局部对象
class Person
{
public:
Person()
{
cout << &#34;Person的默认构造函数调用&#34; << endl;
}
Person(int age)
{
m_Age = age;
cout << &#34;Person的有参构造函数调用&#34; << endl;
}
//拷贝构造函数
Person(const Person& p)
{
cout << &#34;Person的拷贝构造函数调用&#34; << endl;
m_Age = p.m_Age;
}
//析构函数
~Person()
{
cout << &#34;Person的析构函数调用&#34; << endl;
}
int m_Age;
};
//1、用已经创建好的对象来初始化新的对象
void test01()
{
Person p1(18);
Person p2 = Person(p1);
cout << &#34;p2的年龄:&#34; << p2.m_Age << endl;
}
//2、值传递的方式 给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p1(100);
doWork(p1);
}
//3、以值方式 返回局部对象
Person doWork2()
{
Person p;
return p;
}
void test03()
{
Person p = doWork2();
}
/*
编译器优化代码后 release版本代码类似以下:
void doWork2( Person &p ){};
Person p;
doWork2(p);
*/
4、构造函数的调用规则
- 编译器会给一个类 至少添加3个函数 默认构造(空实现) 析构函数(空实现) 拷贝构造(值拷贝)
- 如果我们自己提供了 有参构造函数,编译器就不会提供默认构造函数,但是依然会提供拷贝构造函数
- 如果我们自己提供了 拷贝构造函数,编译器就不会提供其他构造函数
简单总结为:构造函数你可以一个都不写,若是写了拷贝,其他不写就不能使用;若是写了有参构造,那默认构造不写的话就不能使用,但拷贝能用默认的。
class Person
{
public:
Person()
{
cout << &#34;默认构造函数调用&#34; << endl;
}
Person(int age)
{
m_Age = age;
cout << &#34;有参构造函数调用&#34; << endl;
}
Person(const Person& p)
{
m_Age = p.m_Age;
cout << &#34;拷贝构造函数调用&#34; << endl;
}
~Person()
{
cout << &#34;析构函数调用&#34; << endl;
}
int m_Age;
};
void test01()
{
Person p1;//提供拷贝构造后,要自己提供默认构造,否则出错
p1.m_Age = 20;
Person p2(p1);
cout << &#34;p2的年龄为: &#34; << p2.m_Age << endl;
}
5、深拷贝与浅拷贝的问题
- 如果有属性开辟到堆区,利用编译器提供拷贝构造函数会调用浅拷贝带来的析构重复释放堆区内存的问题
- 利用深拷贝解决浅拷贝问题
- 自己提供拷贝构造函数,实现深拷贝
class Person
{
public:
Person(char* name, int age)
{
m_Name = (char*)malloc(strlen(name) + 1);
strcpy(m_Name, name);
m_Age = age;
}
Person(const Person& p)
{
m_Name = (char*)malloc(strlen(p.m_Name) + 1);
strcpy(m_Name, p.m_Name);
m_Age = p.m_Age;
}
~Person()
{
if (m_Name != NULL)
{
cout << &#34;Person析构调用&#34; << endl;
free(m_Name);
m_Name = NULL;
}
}
char* m_Name; //姓名
int m_Age; //年龄
};
void test01()
{
char pc[30] = &#34;唐僧&#34;;
Person p(pc, 18);
cout << &#34;姓名: &#34; << p.m_Name << &#34; 年龄: &#34; << p.m_Age << endl;
Person p2(p);
cout << &#34;姓名: &#34; << p2.m_Name << &#34; 年龄: &#34; << p2.m_Age << endl;
}

若不定义Person(const Person& p)这个拷贝构造函数,将会因为析构函数的释放而出错,因为默认的拷贝构造函数是浅拷贝。
6、初始化列表
- 可以利用初始化列表语法 对类中属性进行初始化
- 语法:构造函数名称后 : 属性(值), 属性(值)... Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
class Person
{
public:
//Person(int a, int b, int c)
//{
// m_A = a;
// m_B = b;
// m_C = c;
//}
//Person() :m_A(10), m_B(20), m_C(30)
//{
//}
//构造函数名称后 : 属性(值), 属性(值)...
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p(10, 20, 30);
cout << &#34;m_A = &#34; << p.m_A << endl;
cout << &#34;m_B = &#34; << p.m_B << endl;
cout << &#34;m_C = &#34; << p.m_C << endl;
}
注释的部分为常规形式。
7、类对象作为类中成员
当其他类对象 作为本类成员,先构造其他类对象,再构造自身,析构的顺序和构造相反
class Phone
{
public:
Phone(string pName)
{
cout << &#34;phone 的有参构造调用&#34; << endl;
m_PhoneName = pName;
}
~Phone()
{
cout << &#34;phone 的析构函数调用&#34; << endl;
}
string m_PhoneName;
};
class Game
{
public:
Game(string gName)
{
cout << &#34;Game 的有参构造调用&#34; << endl;
m_GameName = gName;
}
~Game()
{
cout << &#34;Game 的析构函数调用&#34; << endl;
}
string m_GameName;
};
class Person
{
public:
Person(string name, string pName, string gName) : m_Name(name), m_Phone(pName), m_Game(gName)
{
cout << &#34;Person 的有参构造调用&#34; << endl;
}
void PlayGame()
{
cout << m_Name << &#34;拿着 << &#34; << m_Phone.m_PhoneName << &#34; >> 牌手机,玩着 :&#34; << m_Game.m_GameName << endl;
}
~Person()
{
cout << &#34;Person 的析构函数调用&#34; << endl;
}
string m_Name; //姓名
Phone m_Phone; //手机
Game m_Game; //游戏
};
void test01()
{
//当其他类对象 作为本类成员,先构造其他类对象,再构造自身,析构的顺序和构造相反
Person p(&#34;张三&#34;, &#34;苹果&#34;, &#34;王者荣耀&#34;);
p.PlayGame();
}

8、explicit关键字
explicit用途: 防止利用隐式类型转换方式来构造对象
class MyString
{
public:
MyString(char* str)
{
}
//explicit用途: 防止利用隐式类型转换方式来构造对象
explicit MyString(int len)
{
}
};
void test01()
{
MyString str1(10); //括号法
MyString str2 = MyString(100); //显式法
//MyString str3 = 10; //隐式法 容易引起歧义&#34;10&#34;。通过 explicit屏蔽这种用法。
}
9、new和delete
1 malloc 在C++中的弊端
- 程序员必须确定对象的长度。
- malloc返回一个void*指针,c++不允许将void*赋值给其他任何指针,必须强转。
- malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。
- 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。
2 malloc 和 new 区别
- malloc 和 free 属于 库函数 new 和delete属于 运算符
- malloc不会调用构造函数 new会调用构造函数
- malloc返回void* C++下要强转 new 返回创建的对象的指针
class Person
{
public:
Person()
{
cout << &#34;Person构造函数调用&#34; << endl;
}
Person(int a)
{
cout << &#34;Person有参构造调用&#34; << endl;
}
~Person()
{
cout << &#34;Person析构函数调用&#34; << endl;
}
};
void test01()
{
Person* p = new Person;
delete p;
}
Person* person = new Person;
相当于:
Person* person = (Person*)malloc(sizeof(Person));
if(person == NULL){
return 0;
}
person->Init(); 构造函数
3 注意事项
不要用void*去接受new出来的对象,利用void*无法调用析构函数。公用上面的class Person类的代码。以下的delete p会提示出错。
void test02()
{
void* p = new Person;
delete p;
}
4 利用new创建数组
- 释放数组时候 需要加 []
- Person * pPerson = new Person[10];
- delete [] pPerson; //释放数组时候 需要加[]
- 堆区开辟数组,一定会调用默认构造函数
- 栈上开辟数组,可以没有默认构造
//利用new开辟数组
void test03()
{
//int * pInt = new int[10];
//double * pD = new double[10];
//堆区开辟数组,一定会调用默认构造函数
Person* pPerson = new Person[10];
//释放数组时候 需要加[]
delete [] pPerson;
//栈上开辟数组,可以没有默认构造
//Person pArray[10] = { Person(10), Person(20), Person(20) };
}
10、静态成员
1 静态成员变量
- 所有对象都共享同一份数据
- 编译阶段就分配内存
- 类内声明、类外初始化
- 访问方式有两种:通过对象访问、通过类名访问
- 静态成员变量也是有访问权限,私有权限类外访问不到
class Person
{
public:
//1、静态成员变量:编译阶段就分配了内存 ; 类内声明 ;静态成员变量 所有对象都共享同一份数据
static int m_A;
private:
static int m_B; //私有静态成员变量
};
int Person::m_A = 0; //类外初始化
int Person::m_B = 0;
void test01()
{
//1、通过对象进行访问
Person p1;
cout << p1.m_A << endl;
Person p2;
p2.m_A = 100;
cout << p1.m_A << endl;
//2、通过类名进行访问
cout << Person::m_A << endl;
//静态成员变量 也是有访问权限的,私有权限类外访问不到
//cout << Person::m_B << endl;
}

2 静态成员函数
- 所有对象都共享同一份函数
- 静态成员函数 只可以访问 静态成员变量,不可以访问非静态成员变量
- 静态成员函数 也是有访问权限的
- 静态成员函数 有两种访问方式:通过对象 、通过类名
class Person
{
public:
static int m_A;
//2、静态成员函数,所有对象都共享同一个func函数
static void func()
{
//m_C = 100; //静态成员函数 不能访问非静态成员变量
m_A = 100; //静态成员函数 能访问静态成员变量
cout << &#34;func调用&#34; << endl;
}
int m_C;
private:
static int m_B; //私有静态成员变量
static void func2()
{
}
};
int Person::m_A = 0; //类外初始化
int Person::m_B = 0;
void test02()
{
//通过对象
Person p1;
p1.func();
//通过类名
Person::func();
//Person::func2(); 静态成员函数也是有访问权限的
}
11、单例模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
1 主席类案例
- 通过一个类 只能实例化唯一的一个对象
- 私有化
- 默认构造
- 拷贝构造
- 唯一实例指针
- 对外提供 getInstance 接口,将指针返回
//主席类
class ChairMan
{
public:
static ChairMan* getInstacne() //static ChairMan* singleMan;的对外接口
{
return singleMan;
}
private:
//将构造函数私有化,不可以创建多个对象
ChairMan() {};
ChairMan(const ChairMan&) {}; //禁用掉ChairMan * c3 = new ChairMan(*c1);拷贝方式
private:
//将主席指针 私有化,对外提供只读接口
static ChairMan* singleMan; //类内声明 类外初始化
};
ChairMan* ChairMan::singleMan = new ChairMan; //加了作用域,能访问私有的构造函数
void test01()
{
ChairMan* c1 = ChairMan::getInstacne();
ChairMan* c2 = ChairMan::getInstacne();
//ChairMan * c3 = new ChairMan(*c1);
if (c1 == c2)
{
cout << &#34;c1 = c2&#34; << endl;
}
else
{
cout << &#34;c1 != c2&#34; << endl;
}
}

虽然实例化两个,但其实是同一个。
2 打印机案例
- 和主席类案例一样设计单例模式
- 提供打印功能并且统计打印次数
class Printer
{
public:
static Printer* getInstance()
{
return printer;
}
void printText(string text)
{
m_Count++;
cout << text << endl;
}
int m_Count;
private:
Printer()
{
m_Count = 0;
//cout << &#34;打印机构造调用&#34; << endl;
};
Printer(const Printer& p) {};
static Printer* printer;
};
Printer* Printer::printer = new Printer;
void test01()
{
Printer* p1 = Printer::getInstance();
p1->printText(&#34;入职证明&#34;);
p1->printText(&#34;离职证明&#34;);
p1->printText(&#34;加薪申请&#34;);
cout << &#34;打印机使用次数: &#34; << p1->m_Count << endl;
Printer* p2 = Printer::getInstance();
p2->printText(&#34;调休申请&#34;);
cout << &#34;打印机使用次数: &#34; << p1->m_Count << endl;
cout << &#34;打印机使用次数: &#34; << p2->m_Count << endl;
}

无论是p1用还是p2用,我们都是同一台打印机。因此次数共用。
相关链接
若觉得有用,请不吝 点赞/收藏 ~~~ |
|