记录生活中的点点滴滴

0%

C++学习(一)

这几天学习了C++语言,这是第一部分的记录,有基础语法、面向对象以及文件IO操作等,基础部分还有模板、STL等相关的知识还没学,准备等到下一部分出,先这样吧。还有,学C++真的很爽。

C++简介

C++ 是一种中级语言,C++ 进一步扩充和完善了 C 语言,是一种面向对象的程序设计语言

C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:

  • 封装
  • 抽象
  • 继承
  • 多态

标准的 C++ 由三个重要部分组成:

  • 核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
  • C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
  • 标准模板库(STL),提供了大量的方法,用于操作数据结构等。

Hello World!

1
2
3
4
5
6
7
8
#include<iostream>
using namespace std;
int main(){
cout << "hello world!" << "\n"; // 输出--hello world!

system("pause");
return 0;
}

老惯例,先问好!这就是一个基本的 C++ 文件代码,关键就是第四行的代码,其它的基本上每一个 C++ 文件都会这样写。相比于C语言,基础阶段还有两个蛮大的区别:

声明字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
#include<string>
using namespace std;
int main()
{
//C++风格字符串
string name = "zhangsan";
cout << name << "\n";

//C语言风格字符串
char lisi[] = "lisi";
cout << lisi << "\n";

bool flag = true;
cout << flag << "\n"; // 输出布尔值不是true,而是:1

system("pause");
return 0;
}

输入输出

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;
int main(){

string name; //声明 name 为字符串变量
cin >> name; //接受用户输入,赋给 name
cout << name << "\n"; //输出字符串name的值

system("pause");
return 0;
}

结构体

C语言和C++的结构体都是一样的,我我为什么要写一下这个东西?主要是大一那个时候对结构体没太搞明白,现在学习C++的时候,感觉豁然开朗了一样。大一觉得这东西那么多单词,记不住,也不太会用,现在感觉分分钟敲出来,但还是记录一下吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace std;
//结构体定义
struct student{
string name;
int age;
} stu; //直接创建一个结构体变量stu
int main()
{
//直接用上面声明的那个结构体变量stu ,并为其成员逐个赋值
stu.name = "zhangsan";
stu.age = 10;
cout << "stu.name: " << stu.name << "; stu.age: " <<stu.age << "\n";

//再创建一个结构体变量stu2,并用这种大括号方式为其赋值
struct student stu2 = {"lisi", 20};
cout << "stu2.name: " << stu2.name << "; stu2.age: " <<stu2.age << "\n";

system("pause");
return 0;
}

输出结果:

1
2
stu.name: zhangsan; stu.age: 10
stu2.name: lisi; stu2.age: 20

再来个结构体指针代码,其实就是再弄出来个 结构体指针变量p 指向 结构体变量stu的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
using namespace std;
struct student{
string name;
int age;
};
int main()
{
struct student stu = {"lisi", 20};
struct student * p = &stu;
cout << "stu.name: " << p->name << "; stu.age: " << p->age << "\n";

system("pause");
return 0;
}

输出结果:

1
stu.name: lisi; stu.age: 20

好的,基础语法部分基本上都和C语言差不多,就不多写了。接下来主要写C++的核心编程,面向对象、泛型编程以及STL的基本使用等,C语言好像都没有这些东西,相当于小白从零开始学吧,加油!

内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收,在C++中主要利用new在堆区开辟内存

再看一下 Java 的内存划分:

  • 栈(Stack):存放的都是方法中的局部变量,方法的运行一定要在栈当中进行。局部参数一旦超出作用域立即从栈中消失。

  • 堆(Heap):凡是new出来的东西都在堆里面。堆内存里面的东西都有一个16进制的地址值。堆里面的数据都有默认值规则:如果是整数,默认为0,浮点数默认为0.0,字符默认是’\u0000‘,布尔为false,引用类型为null

  • 方法区(Method Area):存储.class相关信息,包含方法的信息。

  • 本地地方栈(Native Method Stack):与操作系统有关。

  • 寄存器(pc Register):与CPU相关。

引用

*作用: *给变量起别名

语法: 数据类型 &别名 = 原名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int &p = a;

cout << p << endl; // 10
cout << &p << endl; // 0x6ffe14

a = 20;
cout << p << endl; // 20
cout << &p << endl; // 0x6ffe14

system("pause");
return 0;
}

使用引用代替指针交换两个数的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
cout << "a : " << a << endl; // 20
cout << "b : " << b << endl; // 10

system("pause");
return 0;
}

引用的本质

本质:引用的本质在c++内部实现是一个指针常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int &p = a;

cout << p << endl; // 10
cout << &p << endl; // 0x6ffe14

const int * pp = &a;

cout << *pp << endl; // 10
cout << pp << endl; // 0x6ffe14

system("pause");
return 0;
}

函数提高

默认参数

在C++中,函数的形参列表中的形参是可以有默认值的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
int add(int a, int b= 10, int c = 10)
{
return a + b + c;
}
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数

int main()
{
int sum = add(10);
cout << sum << endl;
}

占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
cout << "this is func" << endl;
}

int main() {

func(10,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
#include<iostream>
using namespace std;
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用!" << endl;
}
int main()
{
func();
func(10);

/** 输出结果:
func 的调用!
func (int a) 的调用!
**/

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
#include<iostream>
using namespace std;
class Person
{
public:
string name;
int age;
void printInfo()
{
cout << "name : " << name << " age : " << age << endl;
}
};
int main() {

Person person;
person.name = "zhangsan";
person.age = 18;
person.printInfo(); // name : zhangsan age : 18

system("pause");
return 0;
}

语法: class 类名{ 访问权限: 属性 / 行为 };

我们在设计一个像 Java 中的一个 Person 标准类,有无参、全参构造方法,成员变量用 private 修饰,每一个成员变量都有其 gettersetter 方法

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>
using namespace std;
class Person
{
private:
string name;
int age;
public:
Person(){}
Person(string myName, int myAge)
{
name = myName;
age = myAge;
}
void setName(string myName)
{
name = myName;
}
string getName(){
return name;
}
void setAge(int myAge)
{
age = myAge;
}
int getAge()
{
return age;
}
void printInfo()
{
cout << "name : " << name << " age : " << age << endl;
}
};
int main() {

Person person("lisi", 20);
person.printInfo();

system("pause");
return 0;
}

构造函数与析构函数

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法: ~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
class Dog{
public:
Dog(){
cout << "构造函数执行..." << endl;
}
~Dog(){
cout << "析构函数执行..." << endl;
}
};
int main() {

Dog dog;

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
#include<iostream>
using namespace std;
class Dog{
public:
int age;
Dog(int m_age)
{
age = m_age;
}
//拷贝构造函数,使用引用
Dog(const Dog& p)
{
age = p.age;
}
};
int main() {
Dog d1(18);
Dog d2(d1);

cout << "Dog d2 的 age 为 : " << d2.age << endl;

system("pause");
return 0;
}

输出结果:

1
Dog d2 的 age 为 : 18

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

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 Person{
public:
Person(){}
Person(string name, int age)
{
this->name = name;
this->age = new int(age);
}
Person(const Person& p)
{
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
this->name = p.name;
this->age = new int(*p.age);
}
~Person()
{
cout << "构析函数执行..." << endl;
if(this->age != NULL)
{
delete this->age;
}
}
public:
string name;
int* age;
};
int main() {
Person p1("zhangsan", 20);
Person p2(p1);

cout << "p1的年龄:" << *p1.age << endl;
cout << "p2的年龄:" << *p2.age << endl;

system("pause");

return 0;
}

输出结果:

1
2
3
4
5
p1的年龄:20
p2的年龄:20
请按任意键继续. . .
构析函数执行...
构析函数执行...

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

静态成员

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
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 Dog{
public:
static int num;
static void printStaticNum()
{
cout << "静态方法执行..." << endl;
}

};
int Dog::num = 10;
int main() {
//1.直接通过类名访问
Dog::printStaticNum();

//2.通过对象访问
Dog dog;
dog.printStaticNum();

cout << "num = " << Dog::num << endl;

system("pause");
return 0;
}

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
#include<iostream>
using namespace std;
class Cat{
};
class Dog{
public:
int age;
void print()
{
cout << "age : " << age << endl;
}
};
class Pat{
public:
static int age;
};
int main() {
Cat cat;
cout << sizeof(cat) << endl; //空对象,1字节

Dog dog;
cout << sizeof(dog) << endl; //含有一个成员变量 int age,所以是 4字节
//对象的成员方法不占大小空间 ,靠this指针判别

Pat pat;
cout << sizeof(pat) << endl; //静态成员变量不占对象空间,同cat一样,还是 1字节

system("pause");
return 0;
}

可以得出:

  • 非静态成员变量占对象空间
  • 静态成员变量不占对象空间
  • 函数也不占对象空间,所有函数共享一个函数实例
  • 静态成员函数也不占对象空间

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码

那么问题是:这一块代码是如何区分那个对象调用自己的呢?

C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *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
#include<iostream>
using namespace std;
class Dog{
public:
Dog()
{
}
Dog(int age)
{
this->age = age;
}
Dog& addAge()
{
this->age++;
return *this;
}
public:
int age;
};
int main() {
Dog dog(10);
dog.addAge().addAge().addAge().addAge();
cout << dog.age << endl; // 14

system("pause");
return 0;
}

友元

在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

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

友元的关键字为 friend

友元的三种实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

全局函数做友元

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
#include<iostream>
using namespace std;
class Dog{
//告诉编译器 GoodGay全局函数 是 Dog 类的好基友,可以访问私有内容
friend void GoodGay(Dog * dog);
public:
Dog()
{
}
Dog(int age)
{
this->age = age;
}
private:
int age;
};
void GoodGay(Dog * dog)
{
cout << dog->age << endl;
}
int main() {
Dog dog(10);
GoodGay(&dog); // 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
#include<iostream>
using namespace std;
class Dog{
//告诉编译器 类 Cat 是 Dog 类的好基友,可以访问私有内容
friend class Cat;
public:
Dog(int age)
{
this->age = age;
}
private:
int age;
};
class Cat{
public:
void printDog()
{
Dog dog(10);
cout << dog.age << endl;
}
};
int main() {
Cat cat;
cat.printDog(); // 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
30
31
32
33
34
#include<iostream>
#include<string>
using namespace std;
class Dog;
class Cat{
public:
Cat();
void visit();
public:
Dog *dog;
};
class Dog{
friend void Cat::visit();
public:
Dog(){
this->name = "lisi";
}
private:
string name;
};

Cat::Cat(){
dog = new Dog;
}
void Cat::visit(){
cout << dog->name << endl;
}
int main()
{
Cat cat;
cat.visit(); // lisi
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
30
31
32
33
34
#include<iostream>
#include<string>
using namespace std;
class Person{
public:
Person(){}
Person(int age)
{
this->age = age;
}
Person operator+(const Person& p){
Person t;
t.age = this->age + p.age;
return t;
}
public:
int age;
};
/** 也可以用全局函数实现
Person operator+(const Person& p1, const Person& p2){
Person t;
t.age = p1.age + p2.age;
return t;
}
**/
int main() {
Person p1(10);
Person p2(20);
Person p3 = p1 + p2;
cout << p3.age << endl; //30

system("pause");
return 0;
}

左移运算符重载

作用:可以输出自定义数据类型

实现运算符重载有两种方法:全局函数、成员函数

但是左移的话,成员函数实现不了

为什么???

因为我们要输出一个Person对象p(类似于 Java 中的 toString ),这个p不能再左边,而要在右边 :cout << p,类似于这样,所以要用全局函数:ostream& operator<<(ostream& out, Person& p)

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
#include<iostream>
#include<string>
using namespace std;
class Person{
public:
Person(){}
Person(string name, int age)
{
this->name = name;
this->age = age;
}
public:
string name;
int age;
};

ostream& operator<<(ostream& out, Person& p){
out << "姓名:" << p.name << " 年龄:" << p.age;
return out;
}

int main() {
Person p("zhangsan", 10);
cout << p << endl;
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
30
31
32
33
34
35
36
37
38
39
#include<iostream>
#include<string>
using namespace std;
class MyInt{
friend ostream& operator<<(ostream& out, MyInt myInt);
public:
MyInt(){
num = 0;
}
//后置++
MyInt operator++(int){
MyInt t = *this;
num++;
return t;
}
//前置++
MyInt& operator++(){
num++;
return *this;
}
private:
int num;
};

ostream& operator<<(ostream& out, MyInt myInt){
cout << myInt.num;
return out;
}

int main() {
MyInt mi;
cout << mi++ << endl; // 0
cout << mi << endl; // 1

cout << ++mi << endl; // 2

system("pause");
return 0;
}

总结: 前置递增返回引用,后置递增返回值

赋值运算符重载

c++编译器至少给一个类添加4个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 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
#include<iostream>
#include<string>
using namespace std;
class MyAge{
public:
MyAge(int age){
//将年龄数据开辟到堆区
this->age = new int(age);
}
MyAge& operator=(MyAge &p){
if(age != NULL){
delete age;
age = NULL;
}
age = new int(*p.age);
return *this;
}
~MyAge(){
if(age != NULL){
delete age;
age = NULL;
}
}
public:
int *age;
};

int main() {
MyAge a1(20);
MyAge a2 = a1;

cout << *a2.age << endl;

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
30
31
32
33
34
35
36
#include<iostream>
#include<string>
using namespace std;
class Person{
public:
Person(string name, int age){
this->name = name;
this->age = age;
}
bool operator==(Person &p){
if(this->name == p.name && this->age == p.age){
return true;
}
return false;
}
bool operator!=(Person &p){
if(this->name == p.name && this->age == p.age){
return false;
}
return true;
}
public:
string name;
int age;
};
int main() {

Person p1("zhangsan", 20);
Person p2("lisi", 20);
Person p3("zhangsan", 20);

cout << "p1 == p2 的结果 : " << (p1==p2) << endl;
cout << "p1 == p3 的结果 : " << (p1==p3) << endl;
system("pause");
return 0;
}

运行结果:

1
2
p1 == p2 的结果 : 0
p1 == p3 的结果 : 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
#include<iostream>
#include<string>
using namespace std;
class Func{
public:
void operator()(string text){
cout << text << endl;
}
};
class MyAdd{
public:
int operator()(int a, int b){
return a+b;
}
};
int main() {
Func func;
func("hello world!"); // hello world!

MyAdd add;
int res = add(10, 20);
cout << res << endl; // 30

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
#include<iostream>
#include<string>
using namespace std;
class Father{
public:
int fatherVariable;//父类的变量
Father(){
this->fatherVariable = 10;
}
void fatherMethod(){
cout << "父类的方法..." << endl;
}
};
class Son : public Father{

};
int main() {
Son son;
cout << son.fatherVariable << endl; // 10
son.fatherMethod(); // 父类的方法...

system("pause");
return 0;
}

继承方式

  • 公共继承
  • 保护继承
  • 私有继承

继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;
class Father{
public:
int a;
protected:
int b;
private:
int c;
};
class Son : public Father{
public:
int d;
};
int main() {
Son son;
cout << sizeof(son) << endl; //16
system("pause");
return 0;
}

父类中用 private 修饰的变量是否也继承到子类中了呢?

结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到

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

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 Father{
public:
Father(){
cout << "父类的构造方法执行了..." << endl;
}
~Father(){
cout << "父类的构析方法执行了..." << endl;
}
};
class Son : public Father{
public:
Son(){
cout << "子类的构造方法执行了..." << endl;
}
~Son(){
cout << "子类的构析方法执行了..." << endl;
}
};
void test(){
Son son;
}
int main() {
test();

system("pause");
return 0;
}

运行结果:

1
2
3
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
#include<iostream>
using namespace std;
class Father{
public:
int variable;
Father(){
this->variable = 10;
}
void func(){
cout << "父类的func函数..." << endl;
}
};
class Son : public Father{
public:
int variable;
Son(){
this->variable = 20;
}
void func(){
cout << "子类的func函数..." << endl;
}
};
int main() {
Son son;
// 默认都是子类的
cout << son.variable << endl; // 20
son.func(); // 子类的func函数...

// 可以加作用域访问到父类的
cout << son.Father::variable << endl; // 10
son.Father::func(); // 父类的func函数...

system("pause");
return 0;
}

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

多继承语法

C++允许一个类继承多个类

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

多继承可能会引发父类中有同名成员出现,需要加作用域区分

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
#include<iostream>
#include<string>
using namespace std;
class Father1{
public:
string f1;
Father1(){
cout << "父类Father1构造函数执行..." << endl;
f1 = "Father1";
}
};
class Father2{
public:
string f2;
Father2(){
cout << "父类Father2构造函数执行..." << endl;
f2 = "Father2";
}
};
class Son : public Father1, public Father2{
public:

};
int main() {
Son son;
cout << son.f1 << endl; // Father1
cout << son.f2 << endl; // Father2

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
30
31
32
#include<iostream>
using namespace std;
class Animal
{
public:
int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;

cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {

test01();

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
30
31
32
33
34
35
36
37
38
#include<iostream>
using namespace std;
class Animal
{
public:
virtual speak(){
cout << "动物在说话..." << endl;
}
};
class Dog: public Animal{
public:
speak(){
cout << "小狗在说话..." << endl;
}
};
class Cat: public Animal{
public:
speak(){
cout << "小猫在说话..." << endl;
}
};
void speak(Animal &animal){
animal.speak();
}
int main() {
Animal animal;
speak(animal); //动物在说话...

Dog dog;
speak(dog); //小狗在说话...

Cat cat;
speak(cat); //小猫在说话...

system("pause");

return 0;
}

总结:

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为 抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
class Father{
public:
virtual void func() = 0;
};
class Son : public Father{
public:
void func(){
cout << "Son的func方法执行..." << endl;
}
};
int main() {
Father* t = new Son;
t->func();

system("pause");
return 0;
}

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 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
#include<iostream>
#include<string>
using namespace std;
class Father{
public:
Father(){
}
virtual void speak() = 0;
virtual ~Father(){
cout << "Father的构析函数执行..." << endl;
}
};
class Son : public Father{
public:
string* name;
Son(string name){
this->name = new string(name);
}
void speak(){
cout << *name << "小猫在说话" << endl;
}
~Son(){
if(this->name != NULL){
delete name;
name = NULL;
}
cout << "Son的构析函数执行..." << endl;
}
};
void test(){
Father *t = new Son("tom");
t->speak();
delete t;
}
int main() {
test();

system("pause");
return 0;
}

总结:

​ 1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

​ 2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

​ 3. 拥有纯虚析构函数的类也属于抽象类

文件操作

写文件

步骤如下:

  1. 包含头文件

    1
    #include<fstream>
  2. 创建流对象

    1
    ofstream ofs;
  3. 打开文件

    1
    ofs.open("文件路径", 打开方式);
  4. 写数据

    1
    ofs << "写入的数据" << endl;
  5. 关闭文件

    1
    ofs.close();

文件打开方式:

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

注意: 文件打开方式可以配合使用,利用|操作符

例如:用二进制方式写文件 ios::binary | ios:: out

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
#include<fstream>
using namespace std;
int main() {
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "年龄:20" << endl;
ofs << "性别:男" << endl;
ofs.close();

system("pause");
return 0;
}

读文件

步骤如下:

  1. 包含头文件

    1
    #include<fstream>
  2. 创建流对象

    1
    ifstream ifs;
  3. 打开文件并判断文件是否打开成功

    1
    2
    3
    4
    5
    ifs.open("文件路径", 打开方式);
    if(!ifs.is_open()){
    cout << "文件打开失败" << endl;
    return 0;
    }
  4. 四种方式读文件

  5. 关闭文件

    1
    ifs.close();

实例:

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>
#include<fstream>
using namespace std;
int main() {
ifstream ifs;
ifs.open("test.txt", ios::in);
if(!ifs.is_open()){
cout << "文件打开失败" << endl;
return 0;
}
//第一种方法
// char buff[1024] = {0};
// while(ifs >> buff){
// cout << buff << endl;
// }

//第二种方法
// char buff[1024] = {0};
// while(ifs.getline(buff, sizeof(buff))){
// cout << buff << endl;
// }

//第三种方法
// string buff;
// while(getline(ifs, buff)){
// cout << buff << endl;
// }

//第四种方法
char c;
while((c = ifs.get()) != EOF){
cout << c;
}

ifs.close();

system("pause");
return 0;
}

二进制读文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
#include<fstream>
using namespace std;
class Person{
public:
char name[64];
int age;
};
int main() {
ofstream ofs;
ofs.open("person.txt", ios::out | ios::binary);
Person p = {"张三", 20};
ofs.write((const char *)&p, sizeof(p));

ofs.close();

system("pause");
return 0;
}

二进制写文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<fstream>
using namespace std;
class Person{
public:
char name[64];
int age;
};
int main() {
ifstream ifs;
ifs.open("person.txt", ios::in | ios::binary);
if(!ifs.is_open()){
cout << "文件打开失败..." << endl;
return 0;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名:" << p.name << " 年龄:" << p.age <<endl;

ifs.close();

system("pause");
return 0;
}