类和对象的学习

1.静态成员static

共享一份

类内声明,类外初始化,不属于某一个对象上,所有对象都共享同一份数据

因此静态成员有两种访问方式

1.通过对象来访问,同下即可

2.通过类名来访问

同时静态成员变量也是有访问权限的

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
45
46
47
48
49
50
51
52
53

#include<iostream>
#include<algorithm>
using namespace std;
class Person
{
public:
static int m_a;
int m_c;
//静态成员函数
static void func
{
cout<<"I love you"<<'\n';
}
//同样可以通过对象和类名进行访问
private:
static int m_b;
//类外不能访问
//所有对象共享一份数据
//编译期间分配内存
//类内声明,类外初始化
};
int Person:: m_a = 100;
int Person:: m_b=100;
void test01()
{
//m_c=100;
//静态成员函数不能访问非静态成员变量
//不知道这个变量
Person p1;
cout << p1.m_a<<'\n';
Person p2;
p2.m_a = 200;
cout << p1.m_a <<'\n';
}
void test02()
{
cout<<Person::m_a<<'\n';
//cout<<Person::m_b<<'\n';//这是错误的
}
void test03()
{
//通过对象访问
Person p;
p.func();
//通过类名访问
Person::func();
}
int main()
{
test01();
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
30
31
32
33
34
35
36
37
38
39
40
41
42

//一些基础的赋值操作和析构函数和构造函数的先后顺序问题
#include<iostream>
#include<string>
using namespace std;
class Phone
{
public:
Phone(string name)
{
cout << "手机被赋初值了" << '\n';
m_pname = name;
}
~Phone()
{
cout << "析构函数的调用" << '\n';
}
string m_pname;
};
class Person
{
public:
Person(string name, string pname) :m_name(name), m_phone(pname)
{
cout << "人被赋初值了" << '\n';
}
~Person()
{
cout << "人析构函数的调用" << '\n';
}
string m_name;
Phone m_phone;
};
void test01()
{
Person p("张三", "华为");
cout << p.m_name << "拿着" << p.m_phone.m_pname<<'\n';
}
int main()
{
test01();
}

2.成员变量和成员函数

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

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
#include<iostream>
#include<algorithm>
using namespace std;
//成员变量和成员函数分开存储
class Person
{
int m_a;//非静态成员变量
static int m_b;
void func()
{
}
//成员函数和成员变量是分开存储的
//具体区分见下一节课
static void func
{
}
//同理也不会
//综上所诉,只有非静态成员变量才在类的对象上
};
int Person::m_b=100;
//但不属于类的对象上
void test01()
{
Person p;
cout << "size of p=" << sizeof(p) << '\n';
}
void test02()
{
Person p;
cout << "size of p=" << sizeof(p) << '\n';
}
int main()
{
//test01();
test02();
return 0;
}

test 01()

代码展示结果

空对象占用内存空间是1

//C++编译器给每个空对象也分配一个字节空间

//是为了区分空对象占用的内存位置

//主要是为了区分

//size of p=1

//答案会是1

test 02()

代码展示结果

非静态成员变量属于类的对象上

size of p=4

3. this 指针

上一节课遗留的问题,成员函数要怎么调呢?

this指针指向被调用的成员函数所属的对象

this指针不需要定义

this指针隐含每一个非静态成员

作用

1.解决名称冲突

2.返回对象本身用* 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
#include<algorithm>
using namespace std;
class Person
{
public:
Person(int age)
{
this->age = age;
//age=age是错误的
}
void Personaddage(Person &p)
{
this->age+=p.age;//自身的年林加自己的
}
//因为是void,我们只能调用一次
//如果要调用多次的话,我们需要一个return
Person& Personaddage(Person &p)
{
this->age+=p.age;//自身的年林加自己的
return *this;
//this是指向p2的指针,而this*是p2的本体
}
//如果不是传引用,那就是拷贝构造了
int age;
};
void test01()
{
Person p(18);
cout << p.age;
}
void test02()
{
Person p1(10);
Person p2(20);
p2.Personaddage(p1);
p2.Personaddage(p1).Personaddage(p1);
//链式编程思想
cout<<p2.age<<'\n';
//输出结果是30,非常好用
}
int main()
{
//test01()
test02();
return 0;
}

4.空指针访问成员函数

如果是空指针需要预先说一下,避免程序崩溃

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
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void show()
{
cout << "I love you" << '\n';
}
void showage()
{
if(this==NULL)
{
return ;
}
cout << "your age is" << m_age << '\n';
//报错原因是空指针指向了对象的属性
}
int m_age;
};
void test01()
{
Person* p = NULL;
p->show();
p->showage();


}
int main()
{
test01();



return 0;
}

5.const修饰成员函数

成员函数加上Const之后就是常函数

同理也有常对象

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
#include<iostream>
using namespace std;
class Person
{
public:
void showPerson() const //不加这个的话可以修改值const,否则不可以修改值
//在成员函数后面加上const本质上就是修饰this指针
{
//由于表达式必须是可修改的左值因此无法进行修改
//this指针的本质是指针常量,指针的指向不可修改
//m_a=100;
//this->m_a=100;错误的
this->m_b=1000;
//这句就对了
//mutable
}
int m_a;
mutable int m_b;
//这样就可以修改m_b了
};
void test01()
{
Person p;
p.showPerson();
}
void test02()
{
const Person P;
//属性不允许修改 p.m_a=100;
p.m_b=100;
//可以修改
//常对象只能调用常函数
p.showPerson();
//而不能调用普通的函数
}
int main()
{
test01();
}

6.友元

在程序里,有些私有属性让类外的特殊函数和特殊的类访问,就需要用到友元

友元的目的就是为了让一个函数或者一个类访问另一个类的私有成员

友元的关键字为friend

1.全局函数做友元

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
#include<iostream>
#include<string>
using namespace std;
class Buliding
{
friend void goodgay(Buliding* building);
//意味着全局函数goodgay是他的朋友可以访问私有成员
public:
Buliding()
{
m_keting = "客厅";
m_woshi = "卧室";
//初始化,一经过创建就已经初始化了
}
public:
string m_keting;
//公共
private:
string m_woshi;
//私有
};
void goodgay(Buliding* building)
//以指针的方式进行创建
{
cout << "访问中" << building->m_keting << '\n';
//原本就能访问呢
cout << "访问中" << building->m_woshi << '\n';
//加了Friend才能访问
}
void test01()
{
Buliding building;
goodgay(&building);
}
int main()
{
test01();
return 0;
}

2,类做友元

同样也是把那一句话加上 frirnd 放在函数里面

有一个新知识点,就是如果要类外初始化的话,我们需要在类内想进行声明,在类外加上::这个后才可以进行

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
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
#include<string>
using namespace std;
class Building
{
friend class goodgay;
public:
//类内初始化
Building()
{
m_keting = "客厅";
m_woshi = "卧室";
}
public:
string m_keting;
private:
string m_woshi;
};
class goodgay
{
public:
goodgay();
//如果想要类外初始化的话类内必须先进行声明
//参观函数访问Building public和private
void visit();
Building* buliding;
};
//类外初始化
/*Building::Building()
{
m_keting = "客厅";
m_woshi = "卧室";
}
*/
//类外初始化
//new出一个Building
goodgay::goodgay()
{
buliding = new Building;
}
void goodgay::visit()
{
cout << "正在访问" << buliding->m_keting << " " << buliding->m_woshi;
}
void test01()
{
goodgay gg;
gg.visit();
}
int main()
{
test01();
return 0;
}

3.成员函数做友元

成员函数做友元的时候,那个不做友元的类需要事先声明,然后再进行那个友元的构建,其他和上面两种一样。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<iostream>
#include<string>
using namespace std;
class Building;
class goodgay
{
public:
goodgay();
void visit();
//去访问
// void visit2();
//不可以访问
private:
Building* buliding;
};
class Building
{
friend void goodgay::visit();
public:
Building()
{
m_keting = "客厅";
m_woshi = "卧室";
}
public:
string m_keting;
private:
string m_woshi;
};

goodgay::goodgay()
{
buliding = new Building;
}
void goodgay::visit()
{
cout << "正在访问" << buliding->m_keting << " " ;
cout << "正在访问" << buliding->m_woshi<<'\n';
}
/*void goodgay::visit2()
{
//错误的
//cout << "正在访问" << building->m_keting <<" "<< building->m_woshi<<'\n';
}
*/

void test01()
{
goodgay gg;
gg.visit();
}
int main()
{
test01();
return 0;
}

7.运算符重载

1,加号运算符重载

引言:对于内置运算符,编译器知道如何运算,而其他数据类型呢?

我们有两种方法:成员函数重载在内部,需要用this,全局函数就可以不用This了,直接传入两个Person就可以了

总结1:对于内置数据类型的表达式的运算符是不可以重载的

总结2:不可以滥用运算符,增加代码可行性

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
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
using namespace std;
class Person
{
public:
//成员函数重载运算符
/* Person operator+(Person& p)
{
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
//成员函数的本质Person p3=p1.operstoor+(p2)
*/
int m_a;
int m_b;
};
//全局函数重载运算符
//全局函数的本质就是operator+(p1,p2)
Person operator+ (Person p1,Person p2)
{
Person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
Person operator+ (Person p1, int num)
{
Person temp;
temp.m_a = p1.m_a + num;
temp.m_b = p1.m_b + num;
return temp;
}
//函数重载的,比较方便
void test01()
{
Person p1;
p1.m_a = 10;
p1.m_b = 10;
Person p2;
p2.m_a = 10;
p2.m_b = 20;
Person p3 = p1 + p2;
cout << p3.m_a<<'\n';
cout << p3.m_b << '\n';
Person p4 = p1 + 10;
cout << p4.m_a << '\n';
cout << p4.m_b << '\n';
//没有重载运算符 Person p3=p1+p2;
}
int main()
{
test01();
}

2.左移运算符重载

采取全局函数的重载方法,其次我们需要ostream,作为一个重载的工具。

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
#include<iostream>
using namespace std;
class Person
{
friend ostream& operator<<(ostream& cout, Person& p);
//全局函数做友元
public:
Person(int a,int b)
{
m_a = a;
m_b = b;
}
private:
int m_a;
int m_b;
};
//只能利用全局函数来重载左移运算符
ostream & operator<<(ostream &cout, Person &p)//operator<<(cout,p)
{
cout << "m_a=" << p.m_a << " " << "m_b=" << p.m_b << '\n';
return cout;
//可以把cout随便换,因为引用只是起一个别名
//链式编程思想
}
//需要用标准输出流
void test01()
{
Person p(10,10);

cout << p<<' '<<'\n';
//往后追加输入不能用链式法则,不能继续调用
//内置数据类型
// cout << p;
//没有与这些操作匹配的运算符

}
int main()
{
test01();
}

3.递增运算符重载

前置递增返回引用

后置递增返回值

因为局部变量的引用不能返回

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<iostream>
using namespace std;
//重载递增运算符
//自定义整型变量
class Myinteger
{
public:
//加上友元
friend ostream& operator<<(ostream& cout, Myinteger& myhint);
Myinteger()
{
m_num = 0;
}
//重载++运算符
//重载分为两种
//第一种是前置++运算符
//第二种是后置++运算符
Myinteger & operator++()
{
m_num ++;
return *this;
//先加加再将自身返回
//由于我们要输出的是对象,所以我们要返回自身
//而自身就是* this
//如果我们不返回引用的话,那我们就会出现递增一次后实际上会出现不变的情况
//返回引用是为了一直对一个数据进行递增操作
}
//返回值是不可以作为重载条件
Myinteger operator++(int)
{
//int 代表的是一个占用参数
//可以用来区分前置和后置
//这样我们就可以认为他是后置运算符
//先记录一下当时的结果
//后递增
//后将记录的结果进行返回
Myinteger temp = *this;
m_num++;
return temp;
}
private:
int m_num;
};
//重载左移
ostream& operator<<(ostream& cout, Myinteger &myhint)
{
cout << myhint.m_num;
return cout;
}
void test01()
{
Myinteger myint;
cout << ++(++myint);
}
void test02()
{
Myinteger myint;
cout<< ++myint;
}

int main()
{
test02();
}

4.赋值运算符重载

C++编译器会给一个类添加四个函数‘

第一个是构造函数,无参

第二个是析构函数,无参

第三个是拷贝构造函数,对属性进行值拷贝

第四个是赋值运算符 operator = 对属性进行值拷贝

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
45
46
47
48
49
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_age = new int(age);
}
~Person()
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;

}
}
//堆区内存重复释放
//解决方案:深拷贝->浅拷贝
Person& operator=(Person& p)
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);
return *this;
}
int* m_age;
};
void test01()
{
Person p1(19);
Person p2(20);
p2 = p1;
cout << *p1.m_age<<'\n';
cout << *p2.m_age;

//我们传入一个指针,然后在堆区我们开辟了内存
//用指针接收刚刚好
}

int main()
{
test01();
return 0;
}

5.关系运算符重载

可以让两个自定义类型进行比较

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
//==号的重载,就一般设置一下就行了。
//注意返回的是void
#include<iostream>
using namespace std;
class Person
{
public:
string m_name;
int m_age;
Person(string name, int age)
{
m_name = name;
m_age = age;
}
bool operator==(Person &p)
{
if (this->m_name == p.m_name && this->m_age== p.m_age)
{
return 1;
}
return 0;
}
};
void test01()
{
Person p1("汤姆", 18);
Person p2("汤姆", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << '\n';
}
else {
cout << "不相等" << '\n';
}
}
int main()
{
test01();
return 0;


}

6.函数调用运算符重载

函数调用运算符()也可以重载1

由于重载后的方式非常像函数的调用,称为仿函数

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
#include<iostream>
#include<string>
using namespace std;
class myprint
{
public:
void operator()(string test)
{
cout << test << '\n';
}
};
class myadd
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
void test02()
{
myadd add;
int x=add(199, 199);
cout << x << '\n';
//匿名函数对象
cout<<myadd()(100,100);
// 表达式的前半部分创建匿名对象
}
void test01()
{
myprint my;
my("helloworld");
//由于使用后非常像一个函数因此被称为仿函数
}
int main()
{
test02();
return 0;
}

8.继承

有些类指尖存在继承的关系,因此定义这些类的时候,下级别的成员除了拥有上一级的共性还有自己的特性。

这个时候可以用继承的方式减少重复代码

//基本实现方式在Public后面加上 public :base (继承的类)

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<iostream>
using namespace std;
//普通实现页面
class bage
{
public:
void header()
{
cout << "首页公开登录注册" << '\n';
}
void footer()
{
cout << "帮助中心,交流合作" << '\n';
}
void left()
{
cout << "jave,pythhon" << '\n';
}

};
class java:public bage
{
public:
void content()
{
cout << "java学科视屏" << '\n';
}

};
class python :public bage
{
public:
void content()
{
cout << "python学科视屏" << '\n';
}
};
//继承的好处,减少重复代码
//语法clss子类
//继承方式父类
//子类也称为派生
//父类被称为基类
void test01()
{
java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "-------------" << '\n';
python py;
py.header();
py.footer();
py.left();
py.content();

}
int main()
{
test01();
return 0;
}

继承方式有三种

1.公共继承

2.保护继承

3.私有继承

每一个子类都无法访问父类的私有成员,然后相关的继承方式决定了继承后的成员

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<iostream>
using namespace std;
class bage
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son :public bage
{
public:
void func()
{
m_a = 100;//公共
m_b = 100;//保护
//m_c = 100;
//私有访问不到
}
};
void test01()
{
son a;
a.m_a = 100;
//a.m_b = 100;
//a.m_c = 100;
//保护和私有类外访问不到
}
class son1 :protected bage
{
public:
void func()
{
m_a = 100;//公共
m_b = 100;//保护
//m_c = 100;
//私有访问不到
}
};
void test02()
{
son1 a1;
//a1.m_a = 100;
// 变成保护了
//a.m_b = 100;
//a.m_c = 100;
//保护和私有类外访问不到
}
class son1 :private bage
{
public:
void func()
{
m_a = 100;//公共
m_b = 100;//保护
//m_c = 100;
//私有访问不到
}
};
void test03()
{
son1 a1;
//a1.m_a = 100;
// 变成私有了
//a.m_b = 100;
//a.m_c = 100;
//保护和私有类外访问不到
}
int main()
{
}

存储规则

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
#include<iostream>
using namespace std;
class bage
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son :public bage
{
public:
int m_d;
};
void test01()
{
cout << sizeof son;
}
int main()
{
test01();
}

输出结果是16

继承中构造和析构的顺序

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
#include<iostream>
using namespace std;
class bage
{
public:
bage()
{
cout << "构造" << '\n';
}
~bage()
{
cout << "析构" << '\n';
}
};
class son :public bage
{
public:
son()
{
cout<< "son的构造" << '\n';
}
~son()
{
cout << "son的析构" << '\n';
}
};
void test01()
{
son p1;
}
int main()
{
test01();
}

输出结果:

构造 son的构造 son的析构 析构

顺序如下:先构造父类,再子类,析构相反

继承同名成员的处理方式

当子类与父类出现同名成员,如何通过子类对象,访问子类或父类中同名的成员

访问子类同名成员:直接访问

访问父类成员:需要加作用域

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
#include<iostream>
using namespace std;
class bage
{
public:
int m_a;
bage()
{
m_a = 100;
}
};
class son :public bage
{
public:
son()
{
m_a = 200;
}
int m_a;
};
void test01()
{
son s;
cout << s. m_a<<'\n';
cout << s.bage::m_a;
}
int main()
{
test01();
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
30
31
32
33
#include<iostream>
using namespace std;
class bage
{
public:
static int m_a;
};
int bage::m_a = 100;
//类内声明,类外初始化
class son :public bage
{
public:
static int m_a;
};
int son::m_a = 200;
//同名静态成员属性
void test01()
{
//1.通过对象访问呢
son s;
cout << s.m_a << '\n';
cout << s.bage::m_a<<'\n';
//2.通过类名访问
cout << son::m_a << '\n';
cout << son::bage::m_a;
//两层
//第一个是通过类名方式访问,第二种是访问父类作用域
}

int main()
{
test01();
}

多继承语法

作用域也需要

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
45
46
47
48
49
50
51
#include<iostream>
using namespace std;
class bage1
{
public :
bage1()
{
m_a = 100;
}

int m_a;
};
class bage2
{
public:
bage2()
{
m_a = 200;
}
int m_a;
};
class son :public bage1, public bage2
{
public:
son()
{
m_c = 100;
m_d = 100;
}
int m_c;
int m_d;
};
void test01()
{
son s;
cout << sizeof(s)<<'\n';
cout << s.bage1::m_a<<'\n';
cout << s.bage2::m_a;
}
int main()
{
test01();
/*int a = 100;
int &y = a;//引用
int* p = &a;//指针
int* p2 = &y;
cout << p << '\n';
cout << p2;
//一个是有自己的内存空间,一个没有
*/
}

菱形继承

1
2
菱形继承的概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类,因此被称为菱形继承或者钻石继承
但是这样的继承往往会造成二义性和资源浪费,于是就有虚继承,解决二义性问题,相当于继承了一份
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
#include<iostream>
using namespace std;
class animal
{
public:
int m_age;
};
class sheep :virtual public animal
{

};
class yang :virtual public animal
{

};
class yangtuo : public yang, public sheep
{

};
void test01()
{
yangtuo yangtuoo;
yangtuoo.m_age = 10;
cout << yangtuoo.m_age;
}
int main()
{
test01();
}

9.多态

多态是C++面向对象三大特性之一

分为静态多态和动态多态

静态多态:函数重载,运算符重载,复用函数名

动态多态:派生类,虚函数

二者的区别是

静态多态:编译阶段就确定函数地址

动态多态:运行阶段确定函数地址

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
#include<iostream>
using namespace std;
class Animal
{
public:
//虚函数是必须的
virtual void speak()
{
cout << "动物在说话" << '\n';
}
};
class Cat :public Animal
{
public:

void speak()
{
cout << "小猫在说话" << '\n';
}
};
void dospeak(Animal& animal)
{
animal.speak();
}
void test01()
{
Cat cat;
dospeak(cat);
}
int main()
{
test01();
}

多态优势

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include<iostream>
using namespace std;
//普通和多态
class calculateor
{
public:
int getresulr(string oper)
{
if (oper == "+")
{
return m_num1 + m_num2;
}
else if (oper == "-")
{
return m_num1 - m_num2;
}
else
{
return m_num1 *m_num2;
}
//每一次都要修改源码
}
int m_num1;
int m_num2;

};
void test01()
{
calculateor c;
c.m_num1 = 1;
c.m_num2 = 2;
cout << c.m_num1 << "+" << c.m_num2 << "=" << c.getresulr("+") << '\n';
cout << c.m_num1 << "-" << c.m_num2 << "=" << c.getresulr("-") << '\n';
cout << c.m_num1 << "*" << c.m_num2 << "=" << c.getresulr("*") << '\n';
}
//计算机抽象类
class abstrat
{
public:
int m_num1;
int m_num2;
virtual int getresult()
{
return 0;
}
};
class addcalculator :public abstrat
{
public:
virtual int getresult()
{
return m_num1 + m_num2;
}
};
class jiancalculator :public abstrat
{
public:
virtual int getresult()
{
return m_num1 - m_num2;
}
};
class chencalculator :public abstrat
{
public:
virtual int getresult()
{
return m_num1 * m_num2;
}
};
void test02()
{
//多态使用条件
//父类指针或引用指向子类对象
abstrat* abc=new addcalculator;
abc->m_num1 = 10;
abc->m_num2 = 10;
cout << abc->getresult()<<'\n';
delete abc;
}
int main()
{
//test01();
test02();
}

纯虚函数和抽象类

多态中,父类是无用的,是子类发挥作用,因此可以把virtual写成纯虚函数

抽象类无法实例化对象

如果写了抽象类,子类需要重写抽象类的纯虚函数,不然也是抽象类

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
#include<iostream>
using namespace std;
class base
{
public:
virtual void func() = 0;
//纯虚函数
//首先要是virtual
//抽象类
};
class son : public base
{
public:
virtual void func()
{
cout << "i love you";
}
};
void test01()
{
//base b;
//报错
//new base
//报错
//son s;
//报错
son s;
//不报错
base* basg = new son;
basg->func();
}
int main()
{
test01();
}

多态案例

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include<iostream>
using namespace std;
class abstractdrinking
{
public:
//煮水
virtual void boil() = 0;
//冲泡
virtual void brew() = 0;
//倒入杯中
virtual void pour() = 0;
//加入辅料
virtual void putsome() = 0;
//制作饮品
void makedrink()
{
boil();
brew();
pour();
putsome();
}
};
class caffee:public abstractdrinking
{
//煮水
virtual void boil()
{
cout << "煮农夫山泉" << '\n';
}
//冲泡
virtual void brew()
{
cout << "冲泡咖啡" << '\n';
}
//倒入杯中
virtual void pour()
{
cout << "倒入杯中" << '\n';
}
//加入辅料
virtual void putsome()
{
cout << "加入糖和牛奶" << '\n';
}


};
class tea :public abstractdrinking
{
//煮水
virtual void boil()
{
cout << "煮开水" << '\n';
}
//冲泡
virtual void brew()
{
cout << "冲泡茶叶" << '\n';
}
//倒入杯中
virtual void pour()
{
cout << "倒入杯中" << '\n';
}
//加入辅料
virtual void putsome()
{
cout << "加入柠檬枸杞" << '\n';
}
};
void dowork(abstractdrinking *abs)
{
abs->makedrink();
delete abs;
}
void test01()
{
//制作咖啡
dowork(new caffee);
cout << "-------------" << '\n';
dowork(new tea);
}
int main()
{
test01();
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
//虚析构和纯虚析构
//共性:可以解决父类指针释放子类对象
//都需要有具体的函数实现
class animal
{
public:
//纯虚函数
animal()
{
cout << "animal的构造函数" << '\n';
}
~animal()
{
cout << "animal的析构函数" << '\n';
}
virtual void speak() = 0;

//创建在堆区
};
class cat :public animal
{
public:
cat(string name)
{
cout << "cat的构造函数" << '\n';
m_name = new string(name);
}
~cat()
{
if (m_name != NULL)
{
cout << "析构函数的调用" << '\n';
delete m_name;
m_name = NULL;
}
}
virtual void speak()
{
cout <<*m_name<< "笑猫在说话" << '\n';
}
string* m_name;
};
void test01()
{
animal* animal = new cat("汤姆");
animal->speak();
//父类指针在析构的时候不会调用子类的析构函数,会造成内存的泄露
delete animal;
}
int main()
{
test01();

}

上面代码没有走子类的析构代码,所以有问题。

输出

1
2
3
4
animal的构造函数
cat的构造函数
汤姆笑猫在说话
animal的析构函数

解决方案一:虚析构

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
//虚析构和纯虚析构
//共性:可以解决父类指针释放子类对象
//都需要有具体的函数实现
class animal
{
public:
//纯虚函数
animal()
{
cout << "animal的构造函数" << '\n';
}
//解决方案就是加一个虚析构
//重点virtual
virtual ~animal()
{
cout << "animal的析构函数" << '\n';
}
virtual void speak() = 0;

//创建在堆区
};
class cat :public animal
{
public:
cat(string name)
{
cout << "cat的构造函数" << '\n';
m_name = new string(name);
}
~cat()
{
if (m_name != NULL)
{
cout << "析构函数的调用" << '\n';
delete m_name;
m_name = NULL;
}
}
virtual void speak()
{
cout <<*m_name<< "笑猫在说话" << '\n';
}
string* m_name;
};
void test01()
{
animal* animal = new cat("汤姆");
animal->speak();
delete animal;
}
int main()
{
test01();

}

解决方案二:纯虚析构

需要声明也需要实现

有了纯虚析构,也属于抽象类,无法实例化对象

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
//虚析构和纯虚析构
//共性:可以解决父类指针释放子类对象
//都需要有具体的函数实现
class animal
{
public:
//纯虚函数
animal()
{
cout << "animal的构造函数" << '\n';
}
//纯虚析构
//但不能只声明
virtual ~animal() = 0;
virtual void speak() = 0;
//创建在堆区
};
animal:: ~animal()
{
cout << "纯虚析构" << '\n';
}
class cat :public animal
{
public:
cat(string name)
{
cout << "cat的构造函数" << '\n';
m_name = new string(name);
}
~cat()
{
if (m_name != NULL)
{
cout << "析构函数的调用" << '\n';
delete m_name;
m_name = NULL;
}
}
virtual void speak()
{
cout <<*m_name<< "笑猫在说话" << '\n';
}
string* m_name;
};
void test01()
{
animal* animal = new cat("汤姆");
animal->speak();
delete animal;
}
int main()
{
test01();

}

多态案例:纯虚析构

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//案例三:电脑组装
//每个零件有不同的基类,三台不同的电脑
#include<iostream>
using namespace std;
class cpu
{
public:
virtual void calculate() = 0;
};
class videocard
{
public:
virtual void display() = 0;
};
class meomry
{
public:
virtual void store() = 0;
};
class computer
{
public:
//接收函数
computer(cpu *_cpu1,videocard *_vc,meomry *_meo)
{
cpu1 = _cpu1;
vc1 = _vc;
meo = _meo;
}
//工作函数
void work()
{
cpu1->calculate();
vc1->display();
meo->store();
}
~computer()
{
if (cpu1 != NULL)
{
delete cpu1;
cpu1 = NULL;
}
if (vc1 != NULL)
{
delete vc1;
vc1 = NULL;
}
if (meo != NULL)
{
delete meo;
meo = NULL;
}

}
private:
cpu* cpu1;
videocard* vc1;
meomry* meo;
};
//具体
class intelcpu :public cpu
{
virtual void calculate()
{
cout << "intel的cpu开始工作了" << '\n';
}
};
class intelvideo :public videocard
{
virtual void display()
{
cout << "intel的显卡开始工作了" << '\n';
}
};
class intelmeomry :public meomry
{
virtual void store()
{
cout << "intel的显卡开始工作了" << '\n';
}
};
void test01()
{
cpu* m_intelcpu = new intelcpu;
videocard* m_intelcard = new intelvideo;
meomry* m_intelme = new intelmeomry;
computer* computer1 = new computer(m_intelcpu, m_intelcard, m_intelme);
computer1->work();
delete computer1;
}
int main()
{
test01();
}

文件操作

程序产生的数据都属于临时数据,程序一旦运行起来都会被释放,通过文件可以将数据持久化

C++文件对文件操作需要饱.

文件类型分为两种

第一种是文本文件,文件以文本的 ASCLL 的形式存储在计算机种

第二种是二进制文件,以二进制文件存储在计算机种,用户一般不能直接读懂他们

操作文件有三大类:

  1. :写
  2. :读
  3. :读写操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
写文件操作:
1.包含头文件

<fstream>

2.创建流对象
ofstream ofs

3.打开文件
ofs.open("文件路径",打开方式)

4.写数据
ofs<<"写入的数据"

5.关闭文件
off.close();

1
2
3
4
5
6
7
8
文件打开方式
1.ios::in 读
2.ios::out 写
3.ios:: ate 文件尾部
4.ios::app 追加方式
4.ios::tunc 如果文件存在先删除,再创建
5.ios::binary 二进制方式
注意可以配合使用,使用|即可

写文件

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
#include<iostream>
//1.包含头文件
#include<fstream>
using namespace std;
void test01()
{

//2.创建流对象
ofstream ofs;

//3.指定打开方式
ofs.open("test.txt", ios::out);

//4.写内容
ofs << "姓名:张三" << '\n';
ofs << "年龄:18" << '\n';

//4.关闭
ofs.close();
}
int main()
{
test01();
}
//如果不指定

读文件

读文件与写文件相似,读取方式相对多样

读文件的步骤如下::

1.包含头文件:#include

2.创建流对象

3.打开文件,并判断文件是否打开成功

4.读数据

4种方式进行读取

5.关闭文件

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<iostream>
#include<ostream>
//1.包含头文件
#include<fstream>
using namespace std;
void test01()
{
//2.创建流对象
//3.打开文件,判断
//4.读数据
// 5.关闭文件
ofstream ofs;

//3.指定打开方式
ofs.open("test.txt", ios::out);

//4.写内容
ofs << "姓名:张三" << '\n';
ofs << "年龄:18" << '\n';

//4.关闭
ofs.close();
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << '\n';
return;
}
/*
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << '\n';
}
ifs.close();*/
//第二种
/* char buf[1023] = {0};
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << '\n';
}
*/
//第三种
string buf;
while ( getline(ifs,buf) )
{
cout << buf;
}
//第四种
char c;
//一个一个字符的读
//放到c里面
while((c=ifs.get())!=EOF)
{
cout<<c;
}
}
//第四种不推荐用
//EOF指的是文件的尾部,效率慢
int main()
{
test01();
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
//二进制文件
#include<bits/stdc++.h>
//打开方式要指定为ios::binary
using namespace std;
class Person
{
public:
char m_name[64];
int m_age;
};
void test01()
{
//包含头文件
//创建流对象
//打开文件
//写文件
//关闭文件
ofstream ofs;
ofs.open("person.txt",ios::out|ios::binary);
Person p={"张三",19};
ofs.write((const char *)&p,sizeof(Person));
ofs.close();
}
int main()
{
test01();
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
30
//二进制文件
#include<bits/stdc++.h>
//打开方式要指定为ios::binary
using namespace std;
class Person
{
public:
char m_name[64];
int m_age;
};
void test01()
{
ifstream ifs;
ifs.open("person.txt",ios::in|ios::binary);
if(!(ifs.is_open()))
{
cout<<"打开失败了"<<'\n';
return ;
}
Person p;
ifs.read((char *)&p,sizeof(Person));
cout<<p.m_name<<'\n';
cout<<p.m_age;
ifs.close();
}
int main()
{
test01();
return 0;
}