类和对象

封装

public、protected、private的区别

  • public在类内和类外都可以被访问;
  • protected和private只能在类内被访问;
  • protected可以被子类访问,而private不能;

struct和class的区别

  • struct的默认权限为公有;
  • class的默认权限为私有;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class C1
{
int m_A; //默认是私有权限
};

struct C2
{
int m_A; //默认是公共权限
};

int main() {

C1 c1;
c1.m_A = 10; //错误,访问权限是私有

C2 c2;
c2.m_A = 10; //正确,访问权限是公共

system("pause");

return 0;
}

对象的初始化和清理

构造函数和析构函数

  • 我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现;
  • 构造函数和析构函数编译器会自动调用;
  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用;
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作;
  • 构造函数语法:类名(){};
  • 构造函数可以有参数,因此可以发生重载;
  • 析构函数语法:~类名(){};
  • 析构函数不可以有参数,因此不能发生重载;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person
{
public:
//构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}

};

void test01()
{
Person p;
}

int main() {

test01();

system("pause");

return 0;
}

构造函数

构造函数两种分类方式:按参数分为有参构造无参构造;按类型分为有参构造无参构造

1
2
3
4
5
6
7
//构造函数的三种调用方法
//括号法
Person p('张三')
//显示法
Person p = Person('张三');
//隐式转换法
Person p = '张三';

拷贝构造函数(它是一种函数)

1
2
3
4
5
//拷贝构造函数,可以拷贝一个实例化对象的任何内容
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}

拷贝构造函数调用的时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
using namespace std;
class Person {
public:
int p_age;
Person() {
cout << "调用默认构造函数" << endl;
}
Person(int age) {
p_age = age;
cout << "调用有参构造函数" << endl;
}
Person(const Person& p) {
cout << "调用拷贝构造函数" << endl;
}
~Person() {
cout << "调用析构函数" << endl;
}
};
void fun1(Person p){}
Person fun2()
{
Person p;
return p;
}
void test03()
{
Person p1 = fun2();
}
int main()
{
//情况1:使用一个已经创建完毕的对象初始化一个新对象
//Person p1;
//Person p2(10);
//Person p3(p2);
//情况2:值传递的方法给函数传值
//Person p;
//Person p1 = p; //这样也是可以调用拷贝函数
//fun1(p); //这一行和上一行都可以调用拷贝函数,值传递就相当于进行赋值操作
//情况3:以值方式返回局部对象 没有测试成功
//但是当调用fun2()给p1赋值时,应该是调用拷贝函数的;
test03();
return 0;
}

构造函数的调用规则

  • 默认情况下,编译器会默认创建三个函数:构造函数,析构函数,拷贝构造函数;
  • 在有有参构造函数的情况下,编译器不会创建无参构造函数;
  • 在有拷贝构造函数的情况下,编译器不会自动提供构造函数;

深浅拷贝

  • 浅拷贝带来的问题是堆区内存会被重复释放;

image-20230305162512220

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<iostream>
#include<string.h>
using namespace std;
class Student {
public:
string s_name;
int* s_age;
Student(string name, int age)
{
s_name = name;
s_age = new int(age);
}
Student(const Student& p)
{
s_name = p.s_name;
//s_age = p.s_age; //这是浅拷贝,会带来内存重复释放的问题
s_age = new int(*p.s_age); //深拷贝
}
~Student()
{
if (s_age != NULL)
delete s_age;
}
};
int main()
{
Student s1("张三", 18);
Student s2(s1);
cout <<"s1的name:" << s1.s_name << "age:" << *s1.s_age << endl;
cout <<"s2的name:" << s2.s_name << "age:" << *s2.s_age << endl;
return 0;
}

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

列表初始化

1
2
3
4
5
6
class Student{
public:
string s_name;
int s_age;
Student(string name, int age):s_name(name),s_age(age){}
};

类对象作为类成员

  • 当类中成员是其他类对象时,我们称该对象为对象成员
  • 构造函数调用顺序:先调用对象成员的构造函数,在调用本类的构造函数;
  • 析构函数的调用顺序跟构造函数的调用顺序相反;

静态成员

  • 静态成员分为:静态成员变量和静态成员函数;
  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成团函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
  • 静态成员也是有访问权限的;
1
2
3
4
5
6
//静态成员的访问方式
//方式1 通过对象
Person p;
p.name;
//方式2 通过类名
Person::name;

C++模型和this指针

成员变量和成员函数分开存储

在C++中,成员变量和成员函数是分开存储的,只有非静态成员变量才是属于对象的;

this指针的用途

  • 当形参和成员变量同名时,可用this指针来区分;
  • 在类的非静态成员函数中返回对象本身,可使用return *this;
1
2
3
4
5
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
1
2
3
4
5
6
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//返回对象本身,哪个对象对象调用的这个函数,返回哪个对象上
return *this;
}

空指针访问成员函数

  • 空指针可以访问成员函数,但是当成员函数中有this时就会报错;
  • 为了保持代码的健壮性,可以在成员函数中进行限制;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//空指针访问成员函数
class Person {
public:

void ShowClassName() {
cout << "我是Person类!" << endl;
}

void ShowPerson() {
if (this == NULL) {
return;//如果去掉return,或则把return改成别的就报错了;
}
cout << mAge << endl;
}

public:
int mAge;
};

void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}

int main() {

test01();

system("pause");

return 0;
}

const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象;
  • 常对象只能调用常函数;
  • 常对象可以修改加mutable的成员属性的值;

继承

继承的基本语法

class 子类 : 继承方式 父类

继承后的权限和访问权限

clip_image002

  • 继承后的权限是父类中的权限和继承方式中安全性最高的权限为主;
  • 访问权限由父类决定,在父类中是private的权限不可被访问;

继承中构造函数和析构函数的顺序

  • 构造函数的调用顺序是先调用父类构造函数,再调用子类构造函数;
  • 析构函数的调用顺序和构造函数相反;

继承同名成员处理方式

  • 当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数;
  • 如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域;
1
2
s.func();//调用子类
s.Base::func();//调用父类

继承同名静态成员处理方式

和上述方法一致。

多继承语法

class 子类 :继承方式 父类1 , 继承方式 父类2...

多态

虚函数和纯虚函数

1
2
3
4
5
6
7
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
//纯虚函数
virtual void speak() = 0;

虚函数一般都是没有用的,所哟一般都用纯虚函数;

抽象类

有纯虚函数的类叫抽象类。

切记,有虚函数的类不一定是抽象类,一定要是纯虚函数,有虚函数的类是可以被实例化的。