构造函数和析构函数
构造函数用于初始化
析构函数用于清理
编译器会自动调用这两个函数,完成对象初始化和清理工作。
如果程序员不提供构造和析构,编译器会提供,但是两个函数是空实现。
构造函数:主要用于创建对象时,为对象的成员属性赋值。
析构函数:主要用于对象销毁时系统自动调用,执行一些清理工作。
构造函数的语法: 类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候回自动调用构造,无需手动调用,且只会调用一次。
析构函数的语法: ~类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同加上~
3.构造函数不可以有参数,因此不可以发生重载
4.程序在销毁对象时候回自动调用析构,无需手动调用,且只会调用一次。
#include<iostream>
using namespace std;
#include<string>
class Person{
public:
Person() {
cout << "person的构造函数调用" << endl;
}
~Person() {
cout << "person的析构函数调用" << endl;
}
private:
string name;
int age;
};
int main() {
Person stj;
system("pause");
return 0;
}
构造函数的分类和调用
两种分类:
有参构造(默认构造)和无参构造
按类型分为:
普通构造和拷贝构造
将对象的属性全部拷贝。
#include <iostream>
using namespace std;
// 构造函数按参数分类为: 1.无参构造函数 2.有参构造函数
// 按类型分类为: 1.默认构造函数 2.拷贝构造函数
class Person {
public:
// 无参(默认)构造函数
Person() {
cout << "无参构造函数" << endl;
}
// 有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数" << endl;
}
// 拷贝构造函数
Person(const Person& p) { // Person(const Person& P)
// 将传入的对象的所有属性值拷贝到当前对象中
age = p.age;
cout << "拷贝构造函数" << endl;
}
// 析构函数
~Person() {
cout << "析构函数" << endl;
}
public:
int age;
};
// 构造函数的调用
// 调用无参构造函数
void test1() {
Person p;
}
// 调用有参构造函数
void test2() {
// 1.括号法:常用
Person p0; // 调用无参(默认)构造函数
Person p1(10); // 调用有参构造函数
Person p2(p1); // 调用拷贝构造函数
// 注意1:调用无参构造函数不能带括号,例如:Person p2(),因为编译器会认为这是一个函数声明,不会认为在创建对象
// 测试调用是否成功
cout << "p1的年龄为:" << p1.age << endl;
cout << "p2的年龄为:" << p2.age << endl;
// 2.显式法
Person p3 = Person(10); // 调用有参构造函数
Person p4 = Person(p3); // 调用拷贝构造函数
// 注意2:Person(10)可以放在等号右边,但是单独写是匿名对象,匿名对象的特点是,当前行执行结束后,匿名对象就销毁了
// 注意3: 不要利用拷贝构造函数初始化匿名对象,错误写法:Person(p3); 编译器会认为Person(p3) == Person p3 重定义了
// 3.隐式转换法
Person p5 = 10; // 等价于 Person p4 = Person(10);
Person p6 = p5; // 拷贝构造
// 注意:不能利用拷贝构造函数初始化匿名对象,因为匿名对象没有名字,编译器会认为是对象声明。错误写法:Person p5(p4)
}
int main()
{
test1();
cout << "-------------" << endl;
test2();
return 0;
}
拷贝函数的调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数传参数
3.值方式返回局部对象
如果自己没有定义拷贝构造函数,在上述操作中,编译器会自动创建一个拷贝构造函数,实现临时对象的创建。相当于函数中的临时副本、临时变量。
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
// 1.使用一个已经初始化的对象来初始化一个对象
void test1() {
Person man(100); // 创建一个对象
Person newman(man); // 调用拷贝构造函数
Person newman2 = man; // 调用拷贝构造函数
}
// 2.值传递的方式给函数参数传值 这里在dowork中修改某个属性,原P中的属性不会改变,因为是临时拷贝
void doWork(Person p){}
void test2() {
Person p;
doWork(p);
}
// 3.值方式返回局部对象
Person doWork2() {
Person p1;
return p1;
}
void test3() {
Person p = doWork2();
}
int main() {
test1();
test2();
test3();
return 0;
}
构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
如果已经自定义了某种构造函数,比如说有参的,那么编译器就不会提供相应的构造函数了,再进行无参构造就会报错。但是会默认提供拷贝构造函数
如果只自定义了拷贝构造函数,编译器也不会自动创建无参和有参构造函数。
#include <iostream>
using namespace std;
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person (const Person &p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
// 析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
void test1() {
Person p1(18);
// 如果不写拷贝构造,编译器会自动添加拷贝构造函数,并做浅拷贝操作
Person p2 = p1;
cout << "p2的年龄为:" << p2.age << endl;
}
void test2() {
// 如果用户提供有参构造,编译器不会提供默认构造函数,会提出拷贝构造
Person p1; //此时如果用户没有提供无参构造,编译器会报错
Person p2(10); // 此时如果用户没有提供有参构造,编译器会报错
Person p3(p2); // 此时如果用户没有提供拷贝构造,编译器会提供
// 如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; // 此时如果用户没有提供无参构造,编译器会报错
Person p5(10); // 此时如果用户没有提供有参构造,编译器会报错
Person p6(p5); // 此时如果用户没有提供拷贝构造,编译器会报错
}
int main() {
test1();
test2();
system("pause");
return 0;
}
深拷贝和浅拷贝
浅拷贝:简单的赋值操作
如果使用默认的拷贝函数,会浅拷贝,逐字节的将对象拷贝到另一个对象上(相当于直接赋值)
如果已经在栈上创建了两个对象,也就是局部函数中创建的对象,并且执行默认的拷贝构造函数,那么会进行浅拷贝,这时如果在堆区开辟数据,那么就会把堆区指针直接拷贝过去,最终析构的时候由于(栈区:先进后出,先创建的后释放)会逐个释放,释放两次堆区的数据,所以导致堆区重复释放。
所以需要自定义的拷贝构造函数,拷贝构造函数直接申请一块新的堆区内存,用于存放与拷贝内容相同的数据,这样在析构的时候就不会重复释放同一个堆区的内存了。
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age, int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height); //创建在堆区,用指针接收
}
// 拷贝构造函数
Person (const Person& p) {
cout << "拷贝构造函数!" << endl;
m_age = p.m_age;
//默认实现的代码
//m_age = p.m_age 这里直接是浅拷贝
m_height = new int(*p.m_height);
}
// 析构函数 将堆区数据进行释放
~Person() {
cout << "析构函数!" << endl;
// 何时需要自己写析构函数:平时直接使用编译器默认提供的析构函数,但是涉及到堆区数据,则需要自己写析构函数来释放堆区数据
if (m_height != NULL) { // 这里就会走两个不同堆区的释放
delete m_height;
m_height = NULL; // 防止野指针出现,进行置空操作
}
}
public:
int m_age;
int* m_height;
};
void test01() {
Person p1(18, 180);
Person p2(p1);
cout << "p1的年龄:" << p1.m_age << ",p1的身高:" << *p1.m_height << endl;
cout << "p2的年龄:" << p2.m_age << ",p2的身高:" << *p2.m_height << endl;
}
int main() {
test01();
return 0;
}
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
初始化列表
C++提供了初始化列表语法,用来初始化属性
构造函数():属性1(值1),属性2(值2),()
#include <iostream>
using namespace std;
class Person {
public:
////传统方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
// }
//初始化列表方式初始化,与传统方式初始化不同,更方便
Person(int a, int b, int c):m_A(a), m_B(b), m_C(c){}
void printPerson() {
cout << "m_A:" << m_A << endl;
cout << "m_B:" << m_B << endl;
cout << "m_C:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
// 初始化列表
Person p(1,2,3);
p.printPerson();
return 0;
}
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们成该成员为对象成员
class A {}
class B {
A a;
}
那么创建B对象时,A与B的构造和析构顺序是谁先谁后
我猜 构造的时候先B再A 析构 的时候先A再B 错
先构造类中的其他的类的对象,先有零件才能出来整体。
析构翻过来,先析构本类再析构类内的对象
#include <iostream>
using namespace std;
class Phone {
public:
Phone(string name) {
m_Phonename = name;
cout << "Phone构造" << endl;
}
~ Phone() {
cout << "Phone析构" << endl;
}
string m_Phonename;
};
class Person {
public:
string m_name;
Phone m_phone;
//初始化列表可以告诉编译器调用哪一个构造函数 这里phonename是string类型 m_phone(phonename)=== Phone m_phone = phonename (隐式转换法)
Person (string name, string phonename): m_name(name), m_phone(phonename) {
cout << "Person构造" << endl;
}
~ Person() {
cout << "Person析构" << endl;
}
void playGame() {
cout << m_name << "使用" << m_phone.m_Phonename << "打游戏" << endl;
}
};
void test01() {
Person p("jennie", "samsung");
p.playGame();
}
int main() {
test01();
return 0;
}
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。静态成员分为:
静态成员变量
- 所有的对象共享同一份数据
- (同一个类创建出的所有对象都共享,也就是其中一个对象修改了这个属性,其他的对象属性也跟着修改)
- 在编译阶段分配内存
- 类内声明类外初始化 必须有初始值
静态成员函数
- 所有的对象共享一个函数
- 静态成员函数只能访问静态成员变量
静态成员变量 不属于某个对象,所有的对象都共享同一份数据
因此,静态成员变量有两种访问方式
1.通过对象进行访问
2.通过类名进行访问
#include <iostream>
using namespace std;
// 静态成员变量
class Person {
public:
static int m_A;
private:
static int m_B; // 静态成员变量也是有访问权限的
};
// 静态成员变量初始化
int Person::m_A = 10;
int Person::m_B = 20;
void test01() {
// 静态成员变量不属于某个对象。所有对象都共享同一份数据,因此有2种访问方式:
// 1.通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; // 共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
// 2.通过类名 访问静态成员变量
cout << "m_A = " << Person::m_A << endl;
}
int main() {
test01();
return 0;
}
静态成员函数,静态成员函数不可以访问非静态的成员变量,因为静态函数每个对象都是共用的一个,而非静态变量每个对象都有一个,因此不知道是访问的哪个,因此不能访问。
私有的静态成员函数也无法被类外访问。
#include <iostream>
using namespace std;
class Person {
public:
static int m_A;
int m_B;
static void func() {
cout << "func调用 " << endl;
m_A = 100;
// m_B = 90; // 错误,静态成员函数不能访问非静态成员变量
}
private:
static void func2() {
cout << "func2调用 " << endl;
}
};
int Person::m_A = 10;
// int Person::m_B = 100; // 错误,非静态成员变量不能在类外定义、初始化,也不能通过类名访问
void test01() {
// 静态成员函数2种调用方式
// 1. 通过对象访问
Person p;
p.func();
// 2. 通过类名访问
Person::func();
// Person::func2(); // 私有静态成员函数不能通过类名访问
}
int main() {
test01();
return 0;
}