目录
4 类和对象
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重..,行为有走、跑、跳、吃饭、唱歌..
车也可以作为对象,属性有轮胎、方向盘、车灯…行为有载人、放音乐、放空调....
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
4.2 对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全。
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
4.2.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号 ~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>
using namespace std;
//对象的初始化和清理
class Person {
public:
//1、构造函数,进行对象的初始化
//没有返回值,不用写void
//构造函数的名字必须和类名相同
//构造函数可以有参数,参数类型必须与类成员变量类型一致
//构造函数可以重载,但不能有默认参数
//创建对象时,系统自动调用构造函数进行初始化,且只调用一次
Person() {
cout << "Person() constructor called." << endl;
}
//2、析构函数,进行对象的清理
//没有返回值,不用写void
//析构函数的名字必须和类名相同,但在前面加上波浪符号“~”
//析构函数不能有参数,不能重载
//析构函数在对象销毁时自动调用,一般用来释放资源
//系统自动调用析构函数进行清理,且只调用一次
~Person() {
cout << "~Person() destructor called." << endl;
}
};
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的默认构造函数和析构函数
void test_1() {
//在栈上的数据,test_1执行完毕后,栈空间自动释放,不需要手动调用析构函数
Person p1; //调用构造函数初始化对象
}
int main() {
test_1();//调用test_1函数,创建对象p1,并调用构造函数初始化对象
//Person p1; //调用构造函数初始化对象
system("pause");
return 0;
}
4.2.2 构造函数的分类及调用
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
代码示例:
#include <iostream>
using namespace std;
// 构造函数分类及调用
//分类
// 按照参数分类,无参构造(默认构造函数)、有参构造(参数构造函数)
// 按照类型分类,普通构造函数、拷贝构造函数
class Person {
public:
//构造函数
Person() { cout << "Person()构造函数" << endl; } //默认构造函数
Person(int a) { age = a; cout << "Person(int age)构造函数" << endl; } //有参构造函数
//拷贝构造函数
Person(const Person& p) {
age = p.age; //拷贝构造函数从参数对象拷贝数据到新对象
cout << "Person(const Person& p)拷贝构造函数" << endl;
}
~Person() { cout << "~Person()析构函数" << endl; } //析构函数
int age;//成员变量
};
//调用
void test() {
//1、括号法
//Person p1; //调用默认构造函数
//Person p2(18); //调用有参构造函数
//Person p3(p2); //调用拷贝构造函数
//注意事项
//调用默认构造函数时,不要加括号(),Person p1(); 编译器会认为是声明了一个函数,不会认为在创建对象
//调用有参构造函数时,必须传入参数
//调用拷贝构造函数时,必须传入参数对象
//cout << "p2年龄: " << p2.age << endl;//输出18
//cout << "p3年龄: " << p3.age << endl;//输出18
//2、显示法
//Person p1;//调用默认构造函数
//Person p2 = Person(20); //调用有参构造函数
//Person p3 = p2; //调用拷贝构造函数
//Person(20);//匿名对象,特点:当前执行结束后,系统立即回收
//注意事项
//不要利用拷贝构造函数 初始化匿名对象;编译器会认为 Person p3 == Person p3; 对象声明,重定义
//cout << "显示法,p2年龄: " << p2.age << endl;//输出20
//cout << "显示法,p3年龄: " << p3.age << endl;//输出20
//3、隐式转换法
Person p4 = 25; //调用有参构造函数,隐式转换为Person(int age);相当于Person p4 = Person(25);
Person p5 = p4; //调用拷贝构造函数,隐式转换为Person(const Person& p);相当于Person p5 = Person(p4);
cout << "隐式转换法,p4年龄: " << p4.age << endl;//输出25
}
int main() {
test();
return 0;
}
4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
代码示例:
#include <iostream>
using namespace std;
//拷贝构造函数调用时机
class Person{
public:
Person() {
cout<<"Person() 默认构造函数调用"<<endl;
}
Person(int age){
cout<<"Person(int age) 有参构造函数调用"<<endl;
m_age = age;
}
Person(const Person& p){
cout<<"Person(const Person& p) 拷贝构造函数调用"<<endl;
m_age = p.m_age;
}
~Person(){
cout<<"~Person() 析构函数调用"<<endl;
}
//private:
int m_age;
};
//1、使用一个已经创建完毕的对象去初始化另一个对象
void test_1(){
Person p1(20);//调用有参构造函数
Person p2(p1); //调用拷贝构造函数
cout<<"p2.m_age年龄 = "<<p2.m_age<<endl;
}
//2、值传递的方式给函数参数传值
void test_2(Person p){
}
void test_3(){
Person p;//局部对象,调用默认构造函数
test_2(p); //值传递方式给函数参数传值,调用拷贝构造函数
}
//3、值方式返回局部对象
Person test_4(){
Person p1; //局部对象,调用默认构造函数
cout<<(int*)&p1<<endl; //输出地址
return p1; //值方式返回局部对象,调用拷贝构造函数
}
void test_5(){
Person p = test_4(); //值方式返回局部对象,调用拷贝构造函数
cout<< (int*)&p << endl; //输出地址
}
int main(){
test_1();//输出:Person(int age) 有参构造函数调用 Person(const Person& p) 拷贝构造函数调用
test_3();//输出:Person() 默认构造函数调用 Person(const Person& p) 拷贝构造函数调用
test_5();//输出:Person() 默认构造函数调用 Person(const Person& p) 拷贝构造函数调用 Person(const Person& p) 拷贝构造函数调用 p.m_age年龄 = 0
return 0;
}
4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 1.默认构造函数(无参,函数体为空)
- 2.默认析构函数(无参,函数体为空)
- 3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下
- 如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
代码示例:
#include <iostream>
using namespace std;
// 构造函数调用规则
//1、创建一个类,C++编译器给一个类添加3个函数
//默认构造函数(空实现)
//拷贝构造函数(值拷贝)
//析构函数(空实现)
//2、如果我们写了有参构造函数,那么编译器不会再提供默认构造函数,依然提供拷贝构造函数;
//2、如果我们写了拷贝构造函数,那么编译器不会再提供构造函数,
class Person {
public:
构造函数
//Person() {
// cout << "Person default constructor called. 默认构造函数调用" << endl;
//}
// 构造函数
Person(int age) {
cout << "Person constructor with age called. 带参数的构造函数调用" << endl;
AGE = age;
}
// 拷贝构造函数
Person(const Person& p) {
cout << "Person copy constructor called. 拷贝构造函数调用" << endl;
AGE = p.AGE;// 值拷贝
}
~Person() {
cout << "Person destructor called. 析构函数调用" << endl;
}
// 成员变量
int AGE;
};
//void test_1() {
// Person p; // 调用默认构造函数
// p.AGE = 18; // 给对象成员变量赋值
// Person p2(p); // 调用拷贝构造函数
// cout<<"p2的年龄是:"<<p2.AGE<<endl;
//}
void test_2() {
Person p(20); // 调用带参数的构造函数
Person p2(p); // 调用拷贝构造函数
cout<<"p2的年龄是:"<<p2.AGE<<endl;
}
int main() {
//test_1();//输出:Person default constructor called. 默认构造函数调用
//Person constructor with age called. 带参数的构造函数调用
//Person copy constructor called. 拷贝构造函数调用
//p2的年龄是:18
//Person destructor called. 析构函数调用
//Person destructor called. 析构函数调用
test_2();//输出:Person constructor with age called. 带参数的构造函数调用
//Person copy constructor called. 拷贝构造函数调用
//p2的年龄是:20
//Person destructor called. 析构函数调用
//Person destructor called. 析构函数调用
return 0;
}
4.2.5 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
代码示例:
4.2.6 初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法:构造函数(): 属性1(值1),属性2(值2)...{}
代码示例:
#include <iostream>
using namespace std;
//初始化列表
class Person {
public:
传统方式初始化
//Person(int a, int b, int c) {
// age = a;
// hei = b;
// wei = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) : age(a), hei(b), wei(c) {}
void print() {
cout << "age: " << age << endl;
cout << "hei: " << hei << endl;
cout << "wei: " << wei << endl;
}
private:
int age;
int hei;
int wei;
};
int main() {
Person p1(1, 2, 3);
p1.print();
return 0;
}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
代码示例
class A {}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A有B的构造和析构的顺序是谁先谁后?
代码示例:
#include <iostream>
#include <string>
using namespace std;
// 类对象作为类成员
//手机类
class Phone {
public:
Phone(string pName) {
cout << "Phone 类对象初始化" << endl;
PName = pName;
}
~Phone() {
cout << "Phone 类对象清理" << endl;
}
string PName;
};
//人类
class Person {
public:
//
Person(string name, string pName) : Name(name), Phone(pName) {//
cout << "Person 类对象初始化" << endl;
}
~Person() {
cout << "Person 类对象清理" << endl;
}
string Name; // 成员变量初始化
Phone Phone; // 类对象初始化
};
//当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
void test() {
Person p("Tom", "iPhone 11");
cout << p.Name << "的手机是:" << p.Phone.PName << endl;// 输出:Person 类对象初始化Tom的手机是:iPhone 11
}
int main() {
test();// 输出:Person 类对象初始化Phone 类对象初始化Tom的手机是:iPhone 11Phone 类对象清理Person 类对象清理
return 0;
}
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
- 静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
示例1:静态成员变量
#include <iostream>
using namespace std;
// 静态成员变量
class Person {
public:
//1、类中所有对象共享同一个变量
//2、编译阶段分配内存,运行时初始化
//3、只能在类内部声明,不能在类外定义;类外初始化操作
static int count; // 静态成员变量,
//静态成员变量也是有访问权限的
private:
static int age; // 静态成员变量,
};
int Person::count = 10; // 静态成员变量初始化, 类外初始化操作
int Person::age = 100; // 静态成员变量初始化, 类外初始化操作
void test() {
Person p;
cout << "Person count: " << p.count << endl;// 输出10
Person p1;
p1.count = 20; // 静态成员变量赋值,类外初始化操作
cout << "Person count: " << p1.count << endl;// 输出20
}
void test1() {
// 静态成员变量,不属于某个对象,而是属于类本身,所有对象共享同一个变量
//因此静态变量 有两种访问方式:
//1、通过类名访问,如 Person::count
cout << "通过类名访问Person count: " << Person::count << endl;// 输出20
//cout << "通过对象访问Person age: " << Person::age << endl;// 错误,静态成员变量不能通过对象访问
//2、通过对象访问,如 p.count
Person p2;
cout << "对象访问Person count: " << p2.count << endl;// 输出20
}
int main() {
test(); // 输出10 20
test1(); // 输出
system("pause");
return 0;
}
示例2:静态成员函数
#include <iostream>
using namespace std;
// 静态成员函数
// 静态成员函数只能访问静态成员变量
// 所有对象共享同一份静态成员函数
class MyClass {
public:
// 静态成员函数
static void function() {
count = 100;// 静态成员函数可以访问静态成员变量
//num = 200; // 错误,静态成员函数不能访问非静态成员变量;无法区分
cout << "This is a static member function" << endl;
}
static int count; // 静态成员变量
int num; // 非静态成员变量
//静态成员函数访问权限
private:
static void privateFunction() {
cout << "This is a private static member function" << endl;
}
};
int MyClass::count = 0; // 静态成员变量初始化
// 调用静态成员函数,两种方式
void test() {
//1、通过对象调用静态成员函数
MyClass obj;
obj.function();
//2、通过类名调用静态成员函数
MyClass::function();
//MyClass::privateFunction(); // 错误,无法调用私有静态成员函数
}
int main() {
test();
return 0;
}