c++基础知识详解(上)

内存模型

程序运行前:代码区和全局区

代码区
存放CPU执行的命令
代码区是共享的,是只读的
全局区:
全局变量,静态变量static,常量(字符串常量和const修饰的全局常量)。该区域的数据结束后由操作系统释放

程序运行后:栈区和堆区

栈区:
由编译器自动分配和释放,存放函数的参数和局部变量
注意:不要返回局部变量的地址,因为栈区的数据在函数执行完后的自动释放,第一个数据会做保留,以后不会保留
堆区
由程序员分配和释放,程序结束由操作系统回收
在c++中用new 在堆区开辟内存

new

释放——delete

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>
using namespace std;

void test1()
{
	int*p = new int(10);
	cout << *p << endl;
    delete p;

}
//堆区开辟数组
void test2()
{
	int *arr=new int[10];     //中括号代表创建数组,数组内有10个元素
	int i = 0;
	for (i = 0; i < 10; i++)
		arr[i] = i;
	for (i = 0; i < 10; i++)
		cout << arr[i] << endl;
		delete[]arr;
}

int main()
{
	test1();
	test2();
	system("pause");
	return 0;
}

引用

引用的基本语法

作用:给变量起别名
语法:数据类型 &别名=原名

int a = 10;
	int &b = a;
	cout << b << endl;

注意:1.别名和原名操作的是同一内存空间
2.引用必须初始化,即必须写出别名所指向的原名
3.一旦初始化,不能更改,不能再作为其他变量的别名

引用做函数参数

函数传参时,可以用引用让形参来修饰实参,从而改变实参的值
优点:可以简化指针修饰实参

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 << b << endl;
}

引用做函数的返回值

1.不要做局部变量的引用
2.如果函数的返回值是引用,这个函数调用可以作为左值
第一个b输出10,第二个b输出1000;
因为b是a的别名,test()=1000;把a的值改了,则b作为别名,其值也发生变化

int& test()
{
	static int a = 10;
	return a;
}
int main()
{
	int& b = test();
	cout << b<<endl;
	test() = 1000;
	cout << b << endl;
	
}

引用的本质

本质在c++内部是实现是指针常量,通过const来修饰,指针的指向不可以被更改
int *const p=&a;
c++推荐使用引用技术,因为语法方便,引用的本质是指针常量,但是所有的指针操作编译器都帮我们做了

常量引用

修饰形参,防止误操作
const 修饰后,a的值不可以被更改

void print(const int &a);

函数提高

函数默认参数

1.默认参数是在形参中可以直接赋值作为默认参数
2.形参中某个位置一旦有了默认参数,那么从这个位置往后,从左到右都必须有默认值
3.即使有了默认参数,如果依然有认为传参,那么形参值会赋值为传参的值
4.函数的声明和定义只能其一可以有默认参数,不能同时有默认参数

int  add(int a, int b = 10, int c = 20)
{
	return a + b + c;
}
int main()
{
	
	cout << add(10) << endl;
}

占位参数

语法:返回类型 函数名(数据类型)
1.占位参数即在形参中只写数据类型,而不写变量名,但在调用函数的时候依然需要传占位参数的值
2.占位参数可以有默认参数,传参时则可不传值

void  add(int a, int )
{
	cout << a << endl;
}
int main()
{
	add(10, 10);
}

函数重载

作用:函数名可以相同
条件:1 同一个作用域下
2 函数参数的 类型不同,个数不同或顺序不同
注意:函数的返回值不可以作为重载的条件

引用作为函数重载的条件

有无const 可以作为重载的条件

void  func(int &a);

void func(const int &a);

函数重载遇到默认参数

当函数重载遇到默认参数,会出现二义性

void  func(int a,int b=10);

void func(int a);

类和对象

封装

意义:将属性和行为作为一个整体,表现事物将属性和作为加以权限控制

1 class作为关键字,创建一个类,类中的属性是一些变量,类中的行为是函数,可实施某个操作
2 实例化:通过一个类创建一个对象
3 类中的属性和行为,统称为成员; 属性又称为 成员属性和成员变量;行为又称为 成员函数和成员方法

#include<iostream>
using namespace std;
#define  PI 3.14
class circle
{
	//访问权限  公共权限
public:

	//属性(半径)
	int r;

	//行为(获取圆的周长)——函数
	double  calculate(int r)
	{
		return  2 * PI*r;
	}
};

int main()
{
	circle  c1;
	c1.r = 10;
	cout << "圆的周长=" << c1.calculate(c1.r) << endl;

}

权限

1 公共权限 public 成员:类内可以访问,类外可以访问
2 保护权限 protected 成员:类内可以访问,类外不可以访问 (儿子可以访问父亲的权限)
3 私有权限 private 成员:类内可以访问,类外不可以访问 (儿子不可以访问父亲的权限)
4 struct 和class 均可以作为关键字,创建一个类,区别在于,struct创建的类默认为公共权限,class 默认权限为私有权限
5 成员属性设置私有化的优点 (1)可以自己控制读和写(2)对于写的权限,我们自己可以检测数据的有效性

对象的初始化和清理

构造函数和析构函数

作用:构造函数用于在创建对象时为对象的属性赋值,由编译器自动调用
析构函数:主要作用于对象在销毁时的自动调用,执行一些清理工作
构造函数 类名(){}
1 没有返回值,也没有void
2 函数名与类名相同
3 可以无参数,也可以有参数,可发生重载
4 程序在调用对象时会自动调用,无需手动调用,且只会调用一次
析构函数 ~类名(){}
1 没有返回值,也没有void
2 函数名为 ~类名
3 无参数
4 程序在调用对象时会自动调用,无需手动调用,且只会调用一次
构造和析构都是必须有的实现,如果我们自己不写,编译器会提供一个空实现的构造和析构

构造函数的分类和调用

分类 按照参数分类 :无参函数 有参函数
按照类型分类:普通构造 拷贝构造函数
拷贝构造 类名(const 类名 &变量)
调用:1 括号法
2 显示法
3 隐式转换法

#include<iostream>
using namespace std;
#include<string>

class person
{
public:
	person()     //无参构造
	{
		cout << "person的无参构造函数" << endl;
	}
	person(int a)   //有参构造
	{
		age = a;
		cout << "person的有参构造函数" << endl;
	}
	//拷贝构造函数
	person(const person &p)
	{
		//将传入人的身上的属性,拷贝到我身上
		age = p.age;
		cout << "person的拷贝构造函数" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}
	int age;
};
//调用
void test01()
{
	//1 括号法
	//person  p1;//默认函数的构造
	//person  p2(10);   //传入整形数据,调用有参的函数构造
	//person p3(p2);      //拷贝构造函数


	//注意事项:1 调用默认函数构造时,不要加小括号,加了小括号后,编译器会认为函数声明


	//2 显示法
	person  p1;
	person p2 = person(10);     //有参构造
	person p3 = person(p2);     //拷贝构造   

	//注意事项2  不要利用拷贝构造函数来初始化匿名对象,因为编译器会认为拷贝函数为函数声明
	// person(10)   右侧为匿名对象  特点:当前程序结束后,系统会立即回收匿名对象

	//3 隐式转化法
	person p4 = 10;   //相当于 person p4=person(10); 有参构造
	person p5 = p4;   //进行拷贝构造

}
int main()  
{
	test01();
}

拷贝构造函数的调用时机

1 使用一个已经创建完毕的对象来初始化一个新对象
2 值传递的方式给函数参数传值
3 值方式返回局部对象

构造函数的调用规则

一 默认情况下,c++默认编译器至少给一个类添加3个函数
1 默认构造函数,无参,函数体为空
2 默认析构函数 无参 函数体为空
3 默认拷贝构造函数,对属性进行值拷贝

二 规则
1 如果用户定义了有参构造函数,c++不会提供无参构造函数,但会提供拷贝构造函数
2 如果用户提供了拷贝构造函数,c++不会提供其他构造函数

深拷贝和浅拷贝

浅拷贝:编译器简单的拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
1 在写拷贝函数的时候,用点(.)操作符进行解引用
2 浅拷贝带来的问题就是堆区内存的重复释放
3 浅拷贝带来的问题需要深拷贝进行解决
4 如果属性内有堆区开辟的数据,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
5 深拷贝的本质即再次开次一块新的内存,储存数据

在这里插入代码片class person
{
public:
	//有参构造函数
	person(int age, int height)
	{
		m_age = age;
		m_height = new int(height);
	}
	person(const person  & p)    //拷贝函数调用的时候,用 . (点)操作符即可以进行解引用操作
	{
		m_age = p.m_age;
		cout << "拷贝构造函数调用" << endl;
		m_height = new int(*p.m_height);
	}

	~person()
	{
		cout << "person析构函数调用" << endl;
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
	}

	int m_age;
	int * m_height;

};
//1 使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
	person p1(10,160);
	person p2(p1);
	cout << "p1的年龄" << p1.m_age << "p1的身高" << *p1.m_height << endl;
	cout << "p2的年龄" << p1.m_age << "p2的身高" << *p1.m_height << endl;
}

int main()  
{
	test01();
}

初始化列表

作用:对类中的进行初始化
传统:

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){}

类的对象作为类成员

c++中的成员可以是另一个类的对象,我们称之为对象成员
class A{}
class B
{
A a;
}
当其他类对象作为本类成员时,构造函数先构造类对象,再构造自身,析构函数的顺序与构造函数相反

静态成员

静态成员就是在成员变量和成员函数前加上关键字static ,称为静态成员

静态成员变量

1 所以对象共享同一份数据
2 在编译阶段分配内存
3 类内声明,类外初始化

静态成员函数

1 所有对象共享同一个函数
2 静态成员函数只能访问静态成员变量
3 静态成员不可以访问非静态成员变量,因为无法区分到底是哪个对象

静态函数访问方式

void test01()
{
	//1 通过对象访问
	person p;
	p.func();
	//2 同过类名访问
	person::func();
	
	
}

c++对象模型和this指针

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

只有非静态成员变量属于类的对象上
空对象的内存占用一个字节

this 指针

1 this指针指向被调用的成员函数所属的对象
2 this指针隐含在每一个非静态成员函数内的一种指针
3 this指针不需要定义,直接使用即可
作用:1 解决名称冲突

this->age = age;

2返回对象本身,*this指向的是对象本体,可以链式编程

class person
{
public:
	person(int age)
	{
		this->age = age;
	}
	person& personadd(person &p)     //返回的是引用
	{  
		this->age += p.age;
		//this 是指向p2的指针,*this 指向p2的本体
		return *this;
	}

	int age;
};

void test01()
{
	person p1(10);
	person p2(10);
	//链式编程思想
	//返回的是引用,如果返回的是值,那么在拷贝构造函数返回值的时候创建了一个的对象
	p2.personadd(p1).personadd(p1).personadd(p1);
	cout << p2.age << endl;
	
}

int main()  
{
	test01();
}

友元

作用:让一个函数或者类 访问另一个类中的私有成员
关键走:friend
三种实现方式:全局函数做友元
类做友元
成员函数做友元

全局函数做友元

实现方式:friend void print(People *people);
在类中声名函数,并在函数声明前加上关键字friend

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>

class People
{ 
	//print  全局函数是people 的好朋友,可以访问到私有成员
	friend void print(People *people);
public:
	People()
	{
		a = 1;
		b = 2;
	}

public:
	int a;
private:
	int b;
};
void print(People *people)
{
	cout << people->a<<endl;
	cout << people->b << endl;
}

int main()
{
	People people;
	print(&people);
}

类做友元

目的:让类可以访问另一个类中的成员
实现方式:friend class goodgay; //在另一个前面 friend class 类名

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>

class Building;
class goodgay
{
public:
	goodgay();
	void visit();
private:
	Building*building;
};

class Building 
{
	friend class goodgay;   //
public:
	Building();
public:
	string  sittingroom;  //客厅
private:
	string bedroom;

};
//在类外写构造函数,加上两个::  声明作用域
Building::Building()
{
	sittingroom = "客厅";
	bedroom = "卧室";
}
goodgay::goodgay()
{
	//创建建筑对象
	building = new Building;
}
void goodgay::visit()
{
	cout << "好基友类正在访问:" << building->sittingroom << endl;
	cout << "好基友类正在访问:" << building->bedroom << endl;
}
void test1()
{
	goodgay gg;
	gg.visit();
}

int main()
{
	test1();
}

成员函数做友元

实现方式:friend void goodgay::visit();
在想要访问的类中加上friend 返回值 类名::函数名()
必须加上类名,表示该函数是哪一个类中的

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>

class Building;
class goodgay
{
public:
	goodgay();

	void visit();//让visit 函数访问Building中私有成员
	void visit1();//访问不到

	Building*building;
};

class Building 
{
	//告诉编译器  goodgay类下的visit成员函数可以访问本类中的私有成员
	friend void goodgay::visit();
public:
	Building();
public:
	string  sittingroom;  //客厅
private:
	string bedroom;

};
//在类外写构造函数,加上两个::  声明作用域
Building::Building()
{
	sittingroom = "客厅";
	bedroom = "卧室";
}
goodgay::goodgay()
{
	//创建建筑对象
	building = new Building;
}

void goodgay::visit()//让visit 函数访问Building中私有成员
{
	cout << "好基友类正在访问:" << building->sittingroom << endl;
	cout << "好基友类正在访问:" << building->bedroom << endl;
}

void goodgay::visit1()//访问不到
{
	cout << "好基友类正在访问:" << building->sittingroom << endl;
	//cout << "好基友类正在访问:" << building->bedroom << endl;
}

 
void test1()
{
	goodgay gg;
	gg.visit();
	gg.visit1();
}

int main()
{
	test1();
}

运算符重载

作用:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型

加号运算符重载

加号运算符重载可以让两个类中的数据分别相加
成员函数实现:

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 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;
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>



class person
{
public:

	// 1成员函数重载+号    
	/*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;
	}*/

	int m_a;
	int m_b;
};
//2 全局函数做重载
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;
}

void test1()
{
	person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	person p2;
	p2.m_a = 10;
	p2.m_b = 20;
	//成员函数加号重载本质 person p3=p2.operator+(p2)

	//全局函数重载的本质  person p3=operator+(p1,p2)
	person p3 = p1 + p2; 
	cout << p3.m_a << endl << p3.m_b << endl;
}
int main()
{
	test1();
}

左移运算符重载

左移运算符重载只能使用全局函数实现

ostream & << (ostream &cont, person &p)
{
	cout << "m_a" << p.m_a << "m_b" << p.m_b << endl;
}

递增运算符重载

前置运算符返回引用,后置递增运算符返回值

//重载前置++运算符  返回引用为了一直对一个数据进行递增操作
Myinteger &operator++()
{
	//先进行++运算
	m_num++;
	//再将自身返回
	return *this;
}
//重载后置++运算符
//int 作为占位参数,可以区分前置递增和后置递增
Myinteger operator++(int)
{
	//先记录当前结果
	Myinter temp = *this;
	//后递增  
	m_num++;
	//最后将记录结果返回
	return temp;    //返回的是值,如果返回局部对象的引用,在局部对象程序运行完以后会被释放
}

赋值运算符重载

class Person
{
public:
	Person(int age)
	{
		m_age = new int(age);
	}

	//重载赋值运算符
	Person & operator=(Person &p)
	{
		//先判断m_age是否开辟空间,如果开辟空间,释放后才能指向新的空间
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	

	m_age = new int(*p.m_age);


	//返回自身
	return *this;

	}

	~Person()
	{
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	}

	//年龄指针
	int * m_age;
};   

void test()
{
	Person p1(18);

	Person p2(20);
	
	Person p3(30);

	p3 = p2 = p1;

	cout << "p1的年龄为:" << p1.m_age << endl;

	cout << "p2的年龄为:" << p2.m_age << endl;

	cout << "p3的年龄为:" << p3.m_age << endl;
}


int main()
{
	test();


	return 0;

}

赋值重载运算符

class Person
{
public:
	Person(string name, int age)
	{
		this->m_name = name;
		this->m_age = age;
	}

	bool operator==(Person &p)
	{
		if (this->m_name == p.m_name&&this->m_age == p.m_age)
			return true;
		else
			return false;
	}

	bool operator!=(Person &p)
		{
			if (this->m_name == p.m_name&&this->m_age == p.m_age)
				return false;
			else
				return true;
		}

	string m_name;
	int m_age;
};   

void test()
{
	Person p1("zhang", 18);
	Person p2("zhang", 18);
	if (p1 == p2)
		cout << "p1和p2是相等的" << endl;
	else
		cout << "p1和p2是不相等的" << endl;
}


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

函数调用运算符重载

1 由于重载后使用的方式非常像函数的调用,因此成为仿函数
2 没有固定写法,非常灵活
第一案例是直接打印字符,第二个案例两个数相加,返回其值
匿名函数调用:类名() 这样并没有具体的对象,且在执行完后立即释放

class Myprint
{
public:
	void operator()(string text)
	{
		cout << text << endl;
	}

};  

class Add
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test()
{
	Myprint myprint;
	myprint("hello world");

	Add add;
	int i=add(1, 2);
	cout << i << endl;

	//匿名函数调用
	cout << "Add()(100, 100)=" << Add()(100, 100) << endl;

}


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

继承

继承的基本语法

class 子类:继承方式 父类
class A :public B;
A类称为子类 或派生类
B类称为父类 或基类
继承的好处:可以减少重复的代码

继承方式

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

在这里插入图片描述
1 私有内容不论哪一种继承均不可访问到
2 公共继承父类内的权限不变
3 保护继承公共权限和保护权限均变为保护权限
4 私有继承公共权限和保护权限均变为私有权限

继承中的对象模型

父类中的所有非静态成员属性都会被子类继承下去,父类中的私有成员属性是被编译器隐藏了,因此访问不到,但是确实被继承下去了

继承中构造和析构顺序

继承中先调用父类中的构造函数,再调用子类的构造函数,析构顺序与构造相反
记忆:现有父亲,,再有儿子

继承中同名的处理方式

1 访问子类同名成员:直接访问即可
2 访问父类同名成员:需要加作用域
3 语法: 对象.(点)类名::成员
4 当子类与父类拥有同命的成员函数时,子类会隐藏父类中的同名成员函数(包括重载函数),加作用域可以访问发哦父类中的同名函数

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

静态成员:所有对象共享同一份数据,编译阶段分配内存,类内声明,类外初始化
静态函数:只能访问静态成员变量
和非静态成员处理方式一样,只不过有两种访问方式:对象和类名,均需要加上作用域
1 访问子类同名成员:直接访问即可
2 访问父类同名成员:需要加作用域

多继承语法

c++中允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2…
总结:多继承中如果父类出现了同名情况,子类使用的时候要加作用域

菱形继承(钻石继承)

含义:两个字类继承了同一个父类,又有一个孙子类多继承了两个子类
问题:子类继承两份相同的数据,导致资源浪费其孙子类访问时出现歧义
解决方法:在子类继承父类的继承方式和类名前加上关键字virtual 使公共的父类变为虚基类,继承方式变为虚继承

class Sheep :virtual public Animal{};

底层原理:
两个子类中的数据变为vbptr 虚基类指针指向 vbtable虚基类表格,存储着地址偏移量指向父类中的数据
vbptr v—virtual b—base ptr—pointer 指针
vbtable v—virtual b—base table—表格
在这里插入图片描述

多态

静态多态:函数重载和运算符重载,服用函数名
动态多态:派生类和虚函数实现运行时的动态多态
区别:静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态的函数地址晚绑定,运行阶段确定函数地址

多态的基本语法

多态满足条件:1 有继承关系 2 子类重写父类中的虚函数
多态的使用条件:父类的指针或引用指向子类的对象
使用:在父类中所要继承的函数名前加关键字 virtual
c++中允许父类和子类中的转换,不需要进行强制类型转换

class Animal
{
public:
	virtual  void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public  Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};
void dospeak(Animal & animal)
{
	animal.speak();
}

void test()
{
	Cat cat;
	dospeak(cat);
	Dog dog;
	dospeak(dog);
}
int main()
{
	test();
	return 0;
}

多态的原理剖析

1 virtual 带来了虚函数指针
2 当子类重写父类的虚函数时,子类中的虚函数表内部会替换成子类的虚函数地址
在这里插入图片描述

多态案例——计算器类

优点:1 代码组织结构清晰 2 可读性强 3 利于前期和后期的扩展以及维护

class AbstractCalculator
{
public:
	virtual int getresult()
	{
		return 0;
	}

	int num1;
	int num2;

};

//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
	int getresult()
	{
		return num1 + num2;
	}
};
//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
	int getresult()
	{
		return num1 - num2;
	}
};
//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
	int getresult()
	{
		return num1 * num2;
	}
};



void test()
{
	//创建加法计算器
	//父类的指针或引用指向子类的对象
	AbstractCalculator * abc = new AddCalculator;
	abc->num1 = 10;
	abc->num2 = 10;
	cout << abc->num1 << "+" << abc->num2 << "=" << abc->getresult() << endl;
	delete abc;  //用完了释放指针,但指针的类型依然没有发生变化

	//创建减法计算器
	 abc = new SubCalculator;
	abc->num1 = 10;
	abc->num2 = 10;
	cout << abc->num1 << "-" << abc->num2 << "=" << abc->getresult() << endl;
	delete abc;

	//创建乘法计算器
    abc = new MulCalculator;
	abc->num1 = 10;
	abc->num2 = 10;
	cout << abc->num1 << "*" << abc->num2 << "=" << abc->getresult() << endl;
	delete abc;


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

纯虚函数和抽象类

1 父类中的虚函数实现时毫无意义的,主要都是调用子类中重写的内容,因此将虚函改为纯虚函数
2 语法:virtual 返回值类型 函数名 (参数列表) =0;
3 有了纯虚函数,这个类称为抽象类
4 抽象类无法实例化对象
5 子类必须重写抽象类中的纯虚函数,否则也属于抽象类,不能实例化对象

案例——制作饮品

class AbstractDrinking
{
public:
	//煮水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒水
	virtual void Pour() = 0;
	//加入辅料
	virtual void Put() = 0;
	//制作饮品
	void  makedrink()
	{
		Boil();
		Brew();
		Pour();
		Put();
	}
};
class Coffee :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "加入农夫山泉" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}
	//倒水
	virtual void Pour()
	{
		cout << "倒入水中" << endl;
	}
	//加入辅料
	virtual void Put()
	{
		cout << "加糖" << endl;
	}
	
};
class Tea :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "加入矿泉水" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡茶" << endl;
	}
	//倒水
	virtual void Pour()
	{
		cout << "倒入水中" << endl;
	}
	//加入辅料
	virtual void Put()
	{
		cout << "加柠檬" << endl;
	}

};
void dowork(AbstractDrinking*ptr)
{
	ptr->makedrink();
	delete ptr;  //释放
}

void  test()
{
	dowork(new Coffee);   //用类名开辟对应的对象
	dowork(new Tea);
}

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

虚析构和纯虚析构

1 多态使用时,如果子类中有属性开辟到堆区,那么父类指针通过析构函数释放时无法调用到子类的析构函数
2 产生原因:父类指针在调用析构函数时,并不会调用子类中的析构函数,导致子类中如果有堆区属性,出现内存泄漏
3 解决方式:虚析构或者纯虚析构,此时会调用子类中的析构函数
4 共性:(1)可以解决父类指针释放子类对象 (2)都需要有具体的函数实现
5 区别:如果是纯虚构,该类属于抽象类,无法实例化对象
6 虚析构语法: virtual ~类名(){}
纯虚析构语法: 类内:virtual ~类名()=0;
类外: 类名::~类名(){}

#include<iostream>
using namespace std;
#include<string>


class Animal
{
public:

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}

	virtual void speak() = 0;

	//析构函数加上virtual 关键字,变成虚析构函数
	/*virtual ~Animal()
	{
		cout << "Animal虚析构函数调用!" << endl;
	}*/

	virtual ~Animal() = 0;

};

Animal::Animal()
{
	cout << "Animal 纯虚析构函数调用!" << endl;
}

class Cat :public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_name = new string(name);
	}

	virtual void  speak()
	{
		cout << *m_name << "小猫在说话!" << endl;
	}

	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_name != NULL)
		{
			delete m_name;
			m_name = NULL;
		}

	}



	string *m_name;
};


void test()
{
	Animal *animal = new Cat("Tom");
	animal->speak();

	delete animal;
}

int main()
{
	test();

	system("pause");


	return 0;
	
}

文件操作

(一)
c++中对文件操作需要包含头文件
(二)
文本类型分为两种
文本文件:文件以文本的ASCLL码的形式存储在计算机中
二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它
(三)
操作文件的三大类:
ofstream :写操作
ifstream : 读操作
fstream :读写操作

文本文件

步骤:1 包含头文件
2 创建流对象 ofstream ofs;
3 打开文件 ofs.open(“文件路径”,打开方式);
4 写数据 ofs<<“写入的数据”;
5 关闭文件 ofs.close();

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

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

void test()
{
	ofstream  ofs;
	ofs.open("text.txt", ios::out);
	ofs << "张三" << endl;
	ofs << "男" << endl;
	ofs << "18" << endl;

	//关闭文件
	ofs.close();
}
int main()
{
	test();
	return 0;
}

读文件

1 包含头文件 #include
2 创建流对象 ifstream ifs;
3 打开文件并判断打开是否成功 ifs.open is_open (判断打开是否成功)
4 读取数据
5 关闭文件
判断

if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
	}

第一种方法

	while (ifs >> buff)
	{
		cout << buff << endl;
	}

第二种方法

	//getline 函数中,第一个参数是从文件中读取内容放到的地方,第二个参数是改地址的大小
	char buf[1024] = { 0 };
	while (ifs.getline(buf, sizeof(buf)))
	{
		cout << buf << endl;
	}

第三种方法

//第一个参数是输入流对象,第二个参数是字符串
	string buf;
	while (getline(ifs, buf))
	{
		cout << buf << endl;
	}

第四种方法

	//一个一个读取
	char c;
	while ((c = ifs.get())!=EOF)      //EOF end of file  直到读取到文件末尾  
	{
		cout << c;
	}

void test()
{
	ifstream  ifs;
	ifs.open("text.txt", ios::in);

	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
	}

	char buff[1024] = { 0 };
	//直接读入
	while (ifs >> buff)
	{
		cout << buff << endl;
	}
	//getline 函数中,第一个参数是从文件中读取内容放到的地方,第二个参数是改地址的大小
	char buf[1024] = { 0 };
	while (ifs.getline(buf, sizeof(buf)))
	{
		cout << buf << endl;
	}
	//第一个参数是输入流对象,第二个参数是字符串
	string buf;
	while (getline(ifs, buf))
	{
		cout << buf << endl;
	}

	//一个一个读取
	char c;
	while ((c = ifs.get())!=EOF)      //EOF end of file  直到读取到文件末尾  
	{
		cout << c;
	}


}

二进制写文件

打开方式:ios::binary
函数原型: ostream &wirte(const char *buffer,int len)
字符指针指向内存中的一段存储空间,len是读写的字节数

class Person
{
public:
	char m_name[64];
	int m_age;
};


void test()
{
	//创建输出流对象    同时打开文件
	ofstream ofs("Person.txt", ios::out | ios::binary);

	Person p = { "张三", 18 };

	//写文件   取地址后,转化为字符指针类型
	ofs.write((const char *)&p, sizeof(p));
	
	ofs.close();

}
int main()
{
	test();

	system("pause");
	return 0;

}

二进制读文件

istream &read (char *buffer,int len);

class Person
{
public:
	char m_name[64];
	int m_age;
};


void test()
{
	//创建输出流对象    同时打开文件
	ifstream ifs("Person.txt", ios::out | ios::binary);

	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
	}
	Person p;
	ifs.read((char*)&p, sizeof(p));

	cout << p.m_name << p.m_age << endl;

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不可触碰的殇

前途似海,来日方长

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值