C++面向对象程序设计

C++面向对象程序设计

1. 指针

1.1 关于指针、空指针、野指针的区别

空指针:规定其指向为NULL,用来开辟一个指针时为指明方向。

野指针:在内存中随意开辟一个内存空间来让指针指向,但指针不可以访问地址。

int main() {
	int a = 10;
	int* p = NULL;  //初始化指针指向
	int* p1 = (int*)malloc(sizeof(int));  //系统为指针选择某一内存位置
	int* p2 = (int*)0x1110;
	p = &a;
	cout << &a << endl;  //打印a的地址
	cout << p << endl;   //指针变量p
	cout << "*p:" << *p << endl;  //通过*p访问指针变量指向的内存
	cout << "*p1" << *p1 << endl;  
	cout << "p2" << *p2 << endl;  //无法访问权限
	return 0;
}

1.2 cosnt修饰指针

常量指针:指针的指向可以修改,但是指针指向的值不可以修改。

int a = 10;
int b = 20;
const int *p = &a;
*p = 20;  //错误,指针指向的值不可以修改
p = &b;  //正确,指针指向可以修改

指针常量:指针的指向不可以改,指针指向的值可以改。

int a = 10;
int b = 20;
int * const p = &a;
*p = 20;  //正确,指向的值可以修改
p = &b;  //错误,指针指向不可以修改

const既修饰了指针又修饰了常量:指向和值都不可以修改。

const int * const p = &a;

1.3 指针和数组

利用指针访问数组

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
cout << "第一个元素: " << arr[0] <

1.4 指针和函数

void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}

值传递:实参数值不发生改变,形参发生改变

int main(){
int a = 10,b = 20;
swap(a,b);
}
void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}

地址传递:实参发生改变

swap(int *p1,int *p2){
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
swap(&a,&b);  //调用函数方式

引用传递:函数传参时,可以利用引用的技术让形参修饰实参,可以优化指针修改实参。

void swap(int &p1, int &p2) {
	int temp = p1;
	p1 = p2;
	p2 = temp;
}
int main() {
	int a = 10, b = 20;
	swap(a, b);
	cout << a << " " << b << endl;
	system("pause");
}

2 结构体

基本概念:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。

2.1.1 结构体的定义和使用

struct 结构体名 { 结构体成员列表};

通过结构体创建变量的三种方式:

  • struct 结构体名 变量名
  • struct 结构体名 变量名 = {成员1的值, 成员2的值… };
  • 定义结构体时顺便创建变量

示例:

struct student{
	string name;
	int age;
	int score;
}

2.1.2 结构体数组

作用:将自定义的结构体放入到数组中方便维护。

struct 结构体名 数组名 = { {}, {}, ... {} };

struct student {
	int age;
	int score;
	string name;
	int nums;
};
int main() {
	struct student shen[3] = {
		{21, 98, "李四", 9092},
		{20, 88, "啦啦啦", 45215}
	};
	cout << shen[0].age << endl;
	cout << shen[1].name << endl;
}

2.1.3 结构体指针

  1. 创建结构体变量

    struct student{
        string name;
        int age;
        int score;
    };
    struct student s = {"张三",18,100};//赋初值
    
  2. 通过指针指向结构体变量

    struct student *p = &s;//struct可以省略
    
  3. 通过指针访问结构体变量中的数据

    cout << "姓名:" << p->name << endl;	
    

2.1.4 结构体嵌套结构体

1.作用:结构体的成员可以是另一个结构体

示例:

//学生结构体
struct student {
	string name;
	int age;
	int score;
};
//教师结构体
struct teacher {
	int ID;//职工编号
	string name;
	int age;
	struct student stu;//教师结构体嵌套学生结构体 
};
int main() {
	teacher t;
	t.ID = 123;
	t.name = "老王";
	t.age = 50;
	t.stu.name = "王五";
	t.stu.age = 21;
	t.stu.score = 90;
	cout << "老师姓名:" << t.age << " 老师编号:"  << t.ID << " 老师年龄:"  << t.age << "\n"
		<< "学生姓名:" << t.stu.name <<" 学生年龄:"<

2.1.5 结构体做参数函数

//学生结构体
struct student {
	string name;
	int age;
	int score;
};
//打印学生信息的函数
//值传递(修饰形参不影响实参值)
void printStudent(struct student stu) {
	cout << "姓名:" << stu.name << " 年龄:" <<      	stu.age << " 成绩:" << stu.score << endl;
}
//地址传递(修饰形参影响实参值)
void printStudent(struct student *stu) {
	cout << "姓名:" << stu->name << " 年龄:" << 
    stu->age << " 成绩:" << stu->score << endl;
}
//引用
void printStudent(struct student &stu) {
	cout << "姓名:" << stu.name << " 年龄:" << stu.age << " 成绩:" << stu.score << endl;
}
int main() {
	student stu;
	stu.name = "张三";
	stu.age = 21;
	stu.score = 90;
	printStudent(stu);//调用值传递
	printStudent(&stu);//调用地址传递
	printStudent(stu);//调用引用传递
}	

2.1.6 结构体中const使用场景

作用:用const防止失误操作,编译器会提示不可修改的左值。

对形参前加const,如printfStudent(const student *stu){ }

3 引用

1、引用必须进行初始化。 (int &b;错误)

2、引用一旦初始化后,不可以进行更改。

3.1.1 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参。

优点:可以简化指针修改实参。

3.1.2 引用做函数返回值

//不要返回`局部变量`的引用
int& test(){
	int a = 10;
	return a;//局部变量存放在栈区
}
//函数的调用可以作为`左值`
int& test(){
	ststic int a = 10;//存放在全局变量,全局区上的数据在程序结束后系统释放
}

int main(){
	int &ref = test();
    test() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值
	cout << ref << endl;//编译器最数值进行保留
	cout << ref << endl;//乱码,
}

3.2 引用的本质

指针常量:指针常量是指针指向不可修改,说明引用为什么不可更改

本质:指针常量

int &ref = a;
int *const ref = &a;

3.2 常量引用

作用:常量引用主要是来修饰形参,防止误操作

在函数形参中,可以加const修饰形参,防止形参改变实参

int a = 10;
print(a);
void print(const int & ref)//引用必须引一块合法的内存空间
{
	ref = 20;//表达式必须是可修改的左值
	cout << "ref 为" << ref << endl;
}

加入const时,编译器优化代码,int temp = 10; const int& ref = temp;

4 函数高级

4.1 函数的相关操作

4.1.1 函数默认参数

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

语法:返回值类型 函数名 (参数 = 默认值){}

int sum(int i = 0,int j = 0){...}

默认值左边有右边必须有如下:

int sum(int i = 0,int j,int n){}//错误!!!j和n必须赋默认值

*如果函数声明和函数实现只能有一个有默认参数

4.1.2 函数占位参数

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

占位参数可以有默认值

语法:函数返回值类型 函数名(数据类型){}

void func(int a,int){//占位参数只写数据类型
	cout << "This is a func" << endl;
}
4.1.3 函数重载

函数名相同,提高函数的复用性。

函数重载满足条件:

  • 同意作用域下

  • 函数名称相同

  • 函数参数 类型不同 或者 个数不同 或者 顺序不同

函数返回值不可以作为函数重载的条件

4.1.3.1 函数重载的注意事项
  1. 引用作为重载的条件

  2. 函数重载遇到默认参数

注:函数重载会出现二义性,编程时应避免函数重载遇到默认参数

5 类和对象

C++面向对象的三大特性为:继承、封装、多态

**类和对象:**对象和对象上的属性和行为

5.0 struct和class的区别

  • struct默认访问权限是公共

  • class默认访问权限是私有

5.1 封装

5.1.1封装的意义一

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物

  • 将其属性和行为加以权限控制

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

class Circle{
	public:  //公共访问权限
		int m_r;
		double calculate(){
			return 2 * 3.14 * m_r;
		}	
};
int main(){
	Circle c;  //类的实例化,创建一个类对象
	c.m_r = 10;
	cout << "圆的周长为:" << c.calculate() <
  1. 类中的属性和成员称为成员,属性也称成员属性和成员变量、
  2. 类中的行为称为成员函数成员方法
5.1.2封装的意义二

类在设计时,可以把属性和行为放在不同的权限下,加以控制,访问权限有三种:

  1. public 公共权限
  2. protected 保护权限
  3. private 私有权限

示例:

//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问 子类可以访问父类
//私有权限 private 类内可以访问 类外不可以访问 子类不可以访问父类
class Person{
    //公共权限
    public:
    string m_Name;
    //保护权限
    protected:
    string m_Car;
    //私有权限
    private:
    int m_Password;
    public:
    void func{
        m_Name = "张三";
        m_Car = "劳斯莱斯";
        m_Password = 123456;
    }
};
int main(){
    Person p;  //实例化具体对像
    p.m_Name = "李四";
    //p.m_Car = "奔驰";  //不可访问
    //p.Passworld = 123;  //访问不到
    p.func();
}

5.2.1 类和对象-构造函数

1.构造函数和析构函数
  • 构造函数:用于创建对象时为对象属性赋值,由编译器自动调用

    无返回值,函数名与类名相同、可以有参数,可以进行函数重载

    class Person{
    public:
    	Person();
    }
    Person::Person(){
    }
    
  • 析构函数:用于对象销毁前系统自动调用,执行清理工作

    函数名与类名相同,在名称前加~,函数不可以进行重载

    class Person{
    public:
    	~Person();
    }
    Person::~Person(){
    }
    
  1. 构造函数的分类及调用

    #include
    using namespace std;
    class Person{
    	public:
    		//构造函数 
    		Person(){
    			cout << "Person的构造函数调用" << endl;
    		}
    		//拷贝构造
    		Person(const Person &p){		
    		} 
    };
    int main(){
    	//括号法 
    	Person p1;//默认构造函数 
    	Person p2(10);//有参构造函数
    	Person p3(p2);//拷贝构造函数
    	//显示法
    	Person(10);//匿名对象:当前行执行结束后,系统会立即回收匿名对象 
    	Person p2 = Person(10);//有参构造
    	Person p3 = Person(p2);//拷贝构造 
        //隐式转换法
        Person p4 = 10;//相当于Person p4 = Person(10);
        Person p5 = p4;//拷贝构造
    }
    
2.拷贝构造函数调用时机
  • 使用一个创建完毕的对象来初始化一个新对象

  • 值传递的方式个函数参数传值

  • 以值方式返回局部对象

示例:

//拷贝构造函数调用时机
class Person {
public:
	Person() {
		cout << "Person默认构造函数调用" << endl;
	}
	Person(int Age) {
		m_Age = Age;
		cout << "Person有参构造函数调用" << endl;
	}
	Person(const Person& p) {
		cout << "Person拷贝构造函数调用" << endl;
	}
	~Person() {
		cout << "~Person析构函数调用" << endl;
	}
	int m_Age;
};

//1.使用一个已经创建完毕的对象来初始化一个新的额对象
void test01() {
	Person p1(20);
	Person p2(p1);
}

//2.值传递的方式个函数参数传值
void dowork(Person p) {
}
void test02() {
	Person p;
	dowork(p);
}

//3.值方式返回局部对象
Person dowork2() {
	Person p1;
	cout << (int*)&p1 << endl;//局部对象地址
	return p1;
}
void test03() {
	Person p = dowork2();
	cout << (int*)&p << endl;
}

int main() {
	//test01();
	//test02();
	test03();
}
3.构造函数调用规则
  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

规则
1.如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造。
2. 如果用户定义拷贝构造函数,C++不会再提供其他构造函数

示例:

//构造函数调用规则
//创建一个类,C++会对每一个类添加三个函数
//(默认构造函数、析构函数、拷贝构造函数)
class Person {
public:
	//1.
	Person() {
		cout << "Person的默认参构造函数" << endl;
	}
    /*********************************************/
	//如果我们写了有参构造函数编译器将不会为我们提供默认构造函数
    /*********************************************/
	Person(int age) {
		cout << "Person的有参构造函数" << endl;
		m_Age = age;
	}
    /*********************************************/
	//如果我们写了拷贝构造函数编译器将不会为我们提供默认构造函数以及有参构造函数
    /*********************************************/
	Person(const Person &p) {
		cout << "Person的拷贝构造函数的调用" << endl;
		m_Age = p.m_Age;
	}
	~Person() {
		cout << "~Person的析构函数调用" << endl;
	}
	int m_Age;
};
void test01() {
	Person p;
	p.m_Age = 18;
	Person p2(p);
	cout << "p2的年龄为:" << p2.m_Age << endl;
}

int main() {
	test01();
}

5.3.1 深拷贝浅拷贝

浅拷贝:简单的赋值拷贝

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

示例:

//深拷贝浅拷贝
class Person {
public:
	Person() {
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age,int height) {
		m_Age = age;
		m_Height = new int(height);//手动开辟堆区,必须进行手动释放
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person& p) {//自己设置拷贝构造函数
		cout << "Perosn的拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
		//深拷贝操作
		m_Height = new int(*p.m_Height);
	}
	~Person() {
		//在析构里边将手动开辟的堆区进行释放(*浅拷贝会造成堆区的内存重复释放*)
		//拷贝构造函数释放和有参构造函数都会对同一堆区释放(系统出错)
		if (m_Height != NULL) {
			delete m_Height;
			m_Height = NULL;
		}
		cout << "~Person的析构函数调用" << endl;
	}
	int m_Age;
	int* m_Height;
};
void test01() {
	Person p1(18,160);
	cout << "p1的年龄为:" << p1.m_Age << ",身高为:" << *p1.m_Height << endl;
	Person p2(p1);//我们没有定义拷贝构造函数,编译器对p1进行浅拷贝
	cout << "p2的年龄为:" << p2.m_Age << ",身高为:" << *p2.m_Height << endl;
}
int main() {
	test01();
}

5.4 初始化列表

C++提供了初始化列表的语法,用来初始化属性

语法:构造函数():属性1(值1),属性2(值2)…{}

示例:

class Person {
public:
	/*传统初始化操作*/
	//Person(int a,int b,int c) {
	//	cout << "Person的有参构造函数" << endl;
	//		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) {//冒号位于形参列表之后
		cout << "Person的有参构造函数" << endl;
	}
	int m_A;
	int m_B;
	int m_C;
};
void test01() {
	//Person p(10, 20, 30);
	Person p(30,20,10);
	cout << "m_A = " << p.m_A << endl;
	cout << "m_B = " << p.m_B << endl;
	cout << "m_C = " << p.m_C << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

5.5 类对象作为类成员

C++类中成员可以是另一个类对象,我们称该成员为对象成员

示例:

//当其他类对象作为本类成员,构造时候先构造类对象,在构造自身类
//析构函数顺序
class Phone {
public:
	//手机品牌
	Phone(string pName) {
		m_PName = pName;
		cout << "Phone的有参构造函数" << endl;
	}
	~Phone() {
		cout << "~Phone的析构函数调用" << endl;
	}
	string m_PName;
};
class Person{
public:
	//姓名
	Person(string name, string pName) :m_Name(name),m_Phone(pName){
		cout << "Person的有参构造函数" << endl;
	}
	~Person() {
		cout << "Person的析构函数调用" << endl;
	}
	string m_Name;
	//手机
	Phone m_Phone;
};
void test01() {
	Person p("张三", "苹果MAX");
	cout << p.m_Name << "拿着:" << p.m_Phone.m_PName << endl;
}
int main() {
	test01();
}

5.6 静态成员

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

  1. 静态成员变量

    • 所有对象共享同一份数据

    • 在编译阶段分配内存

    • 类内声明,类外初始化

  2. 静态成员函数

    • 所有对象共享一个函数

    • 静态成员函数只能访问静态成员函数

示例:静态成员变量

#include
using namespace std;
class Person {
public:
	//静态成员函数(不可以访问非静态成员变量)
	static void func() {
		cout << "static void func调用" << endl;
	}
	static int m_A;//静态成员变量(类内声明,类外初始化)
};

int Person::m_A = 0;

void test01() {
	//通过对象访问
	Person p;
	p.func();
	//通过类名访问
	Person::func();
}
int main() {
	test01();
}

6.1 C++对象模型和this指针

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

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

//成员变量和成员函数是分开存储的
class Person {
public:
	//C++编译器会给每一个空对象分配一个字节空间,是为了区分空对象占内存的位置
	Person() {
		cout << "Person的默认构造函数调用" << endl;
		//m_A = 0;
	}
	int m_A;//非静态成员变量,属于类的对象上
	static int m_B;//静态成员变量,不属于类对象上
	void func(){}//静态成员函数,不属于类对象上
	static void func2(){}//静态成员函数,不属于类的对象上
};
void test01() {
	Person p;
	cout << "size of p = " << sizeof(p) << endl;
}
int main() {
	test01();
}

6.2 this指针

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

用途:

  • 当形参和成员变量同名时,可以用this指针来区分

  • 在类的非静态成员函数中返回对象本身,可使用return *this;

class Person {
public:
	Person(int age) {
		this->age = age;//this指针指向的是被调用的成员函数所属的对象
	}
	Person& PersonAddage(const Person& p) {
		this->age += p.age;
		return *this;
	}
	int age;
};
void test01() {
	Person p(2);
	cout << "p 的年龄:" << p.age << endl;
	Person p1(p);
	p1.PersonAddage(p).PersonAddage(p);
	cout << "p1 的年龄:" << p1.age << endl;
}
int main() {
	test01();
	return 0;
}

6.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

示例:

//空指针访问成员函数
using namespace std;
class Person {
public:
	void ShowClassName() {
		//空指针可以访问成员函数的
		cout << "我是Person类!" << endl;
	}
	void ShowPersonAge() {
		if (this == NULL)
			return;
		//传入的指针为NULL
		cout << "age = " << this->m_Age << endl;
	}
	int m_Age;
};
void test01() {
	Person* p = NULL;//不是对象,而是一个空指针
	p->ShowClassName();
	p->ShowPersonAge();
}
int main() {
	test01();
}

6.4 const修饰成员函数

常函数:

  • 成员函数后加const后为常函数

  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const为常对象

  • 常对象只能调用常函数

示例:

//常函数、常对象
class Person {
public:
	//this指针的本质是指针常量,指针的指向是不可以修改的
	//(指针指向的值可以修改)
	void ShowPerson() const{
		this->m_B = 100;
		//this->m_A = 100;
		//this = NULL;//this 指针不可以修改指针的指向
	}
	void ShowPersonAge() {
		cout << "ShowPersonAge函数调用" << endl;
		this->m_A = 100;//必须是可以修改的左值时
	}
	void func() {
		m_A = 100;
	}
	int m_A;
	mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值
};

void test01() {
	Person p;
	p.ShowPerson();
	//p->ShowPersonAge();
}
//常对象
void test02() {
	const Person p;//在对象前加const,变为常对象
	//p.m_A = 100;
	p.m_B = 100;//m_B是特殊值,在常对象下也可以修改

	/*常对象只能调用常函数*/
	p.ShowPerson();
	//p.func();
}
int main() {
	test01();
}

7 友元

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

7.1.1 全局函数做友元

class Building {
public:
	//gooGay全局函数是Building好朋友,可以访问Building中的私有成员
	friend void goodGay(Building* building);
	Building() {
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
//全局函数
void goodGay(Building *building) {
	cout << "好基友的全局函数正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友的全局函数正在访问:" << building->m_BedRoom << endl;
}
int main() {
	Building building;
	goodGay(&building);
}

7.1.2 类做友元

class Building;
class GoodGay {
public:
	void visit();//访问Building中的属性
	GoodGay();
	Building * building;
};
class Building {
	friend class GoodGay;//类作为友元
public:
	Building();
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
GoodGay::GoodGay() {
	//创建一个建筑物对象
	building = new Building;
}
void GoodGay::visit() {
	cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
int main() {
	GoodGay gg;
	gg.visit();
}

7.1.3 成员函数做友元

class Building;
class GoodGay {
public:
	GoodGay();
	Building* building;
	void visit1();//访问Building中的私有成员
	void visit2();//不可以访问Building中的私有成员
};
class Building {
	friend void GoodGay::visit1();//告诉编译器GoodGay类下的成员函数作为本类的好朋友可以访问本类的私有成员
public:
	Building();
	string m_SittingRoom;
private:
	string m_BedRoom;
};
GoodGay::GoodGay() {
	building = new Building;
}
Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
void GoodGay::visit1() {
	cout << "visit1正在访问:" << building->m_SittingRoom << endl;
	cout << "visit1正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit2() {
	cout << "visit2正在访问:" << building->m_SittingRoom << endl;
	//cout << "visit2正在访问:" << building->m_BedRoom << endl;
}
int main() {
	GoodGay gg;
	gg.visit1();
	gg.visit2();
}

8 C++运算符重载

8.1.1.运算符重载加法

class Person {
public:
	int m_A;
	int m_B;
	Person operator+ (Person& p);//声明加法运算符
};
//成员函数就行重载
Person 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;
}
void test01(Person &p1,Person &p2) {
	p1.m_A = 1;
	p1.m_B = 11;
	p2.m_A = 2;
	p2.m_B = 22;
}
void test02(Person& p1, Person& p2) {
	Person p3 = p1 + p2;//本质为:Person p3 = p1.operator+(p2);
	cout << "p3.m_A = " << p3.m_A << "\np3.m_B = " << p3.m_B << endl;
}
int main() {
	Person p1;
	Person p2;
	test01(p1,p2);
	test02(p1, p2);
	return 0;
}
//通过全局函数进行重载
#include
using namespace std;
class Person {
public:
	int m_A;
	int m_B;
	//Person operator+ (Person& p1, Person& 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 i) {
	Person temp;
	temp.m_A = p1.m_A + i;
	temp.m_B = p1.m_B + i;
	return temp;
}
int main() {
	Person p1, p2;
	p1.m_A = 1;
	p1.m_B = 11;
	p2.m_A = 2;
	p2.m_B = 22;
	Person p3 = p1 + p2;//本质是:Person p3 = operator+(p1,p2);
	Person p4 = p1 + 4;//运算符重载也可以发生函数重载
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
	cout << "p4.m_A = " << p4.m_A << endl;
	cout << "p4.m_B = " << p4.m_B << endl;
}

8.1.2.运算符重载左移

class Person {
public:
	friend ostream& operator<< (ostream& cout, Person& p);
	void Getdata(int A,int B) {
		m_A = A;
		m_B = B;
	}
private:
	int m_A;
	int m_B;
	//利用成员函数重载,左移运算符 p.operaotr<<(cout)  简化版本为:p << cout ;
	//void operator<< (cout) {}//不建议使用成员函数进行左移运算符进行重载
};
//利用全局函数对左移运算符进行重载
ostream &operator<< (ostream &out, Person &p) {//本质: operator<< (cout , p)   简化cout << p;
	out << "m_A = " << p.m_A << "\nm_B = " << p.m_B << endl;
	return out;
}
int main() {
	Person p;
	p.Getdata(10,10);
	//p.m_A = 10;
	//p.m_B = 10;
	cout << p << "HelloWorld" <

8.1.3.运算符重载递增

class MyInteger {
	friend ostream& operator<< (ostream& out, MyInteger myint);
public:
	MyInteger() {
		m_Num = 0;
	}
	//重载前置++运算符(返回引用)
	MyInteger& operator++() {//返回引用为了是对一个数据进行操作
		//先进行++运算
		m_Num++;
		return *this;
	}
	//重载后置++运算符(返回为值)
	MyInteger operator++(int) {//int为占位参数
		//先 记录当时结果
		MyInteger temp = *this;
		//后 递增
		m_Num++;
		//最后将记录结果返回
		return temp;
	}
private:
	int m_Num;
};

ostream& operator<< (ostream& out, MyInteger myint) {
	cout << myint.m_Num;
	return cout;
} 

int main() {
	MyInteger myint;
	cout << ++(++myint) << endl;
	cout << myint << endl;
	MyInteger myint2;
	cout << myint2++ << endl;
	cout << myint2 << endl;
}

8.1.4.运算符重载赋值

//重载复制运算符
class Person {
public:
	Person(int age) {
		m_Age = new int(age);
	}
	~Person() {
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
	}
	int *m_Age;
	//重载赋值运算符
	Person& operator= (Person& p) {
		//编译器提供浅拷贝
		//m_Age = p.m_Age;
		//先判断是否有属性在堆区,如果有先释放干净
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
		m_Age = new int(*p.m_Age);
		return *this;
	}
};
int main() {
	Person p1(18);
	Person p2(20);
	Person p3(22);
	p3 = p2 = p1;//赋值
	cout << "p1的年龄:" << *p1.m_Age << endl;	
	cout << "p2的年龄:" << *p2.m_Age << endl;
	cout << "p3的年龄:" << *p3.m_Age << endl;
}

8.1.5.运算符重载关系

//重载关系运算符
class Person {
public:
	Person(string name,int age) {
		m_Name = name;
		m_Age = age;
	}
	bool operator==(Person &p) {
		if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) {
			return true;
		}
		else {
			return false;
		}
	}	
	string m_Name;
	int m_Age;
};
int main() {
	Person p1("Tom", 18);
	Person p2("Jerry", 18);
	if (p1 == p2) {
		cout << "p1 和 p2 相等。" << endl;
	}
	else {
		cout << "p1 和 p2 是不相等的。" << endl;
	}
}

8.1.6.运算符重载函数

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

由于重载后使用方法非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

class MyPrint {
public:
	//重载函数调用
	void operator()(string text) {
		cout << text << endl;
	}
};
int main() {
	MyPrint myFunc;
	myFunc("Hello world!");
}

9 继承

继承是面相对象三大特征之一

9.1.2 继承基本语法

下级别的成员除了拥有上一级的共性,还有自己的特性。

class Java {
public:
	void header() {
		cout << "首页、公开课、登录、注册。。。(公共头部)" << endl;
	}
};
//Python继承Java
class Python :public Java {
public:
	void func1() {
		cout << "Python视频课程" << endl;
	}
};
int main() {
	Python py;
	py.header();//继承Java类的header函数
	py.func1();
}

9.1.3 继承的方式

公有继承:继承类型与父类一致。

保护继承:父类除私有数据成员(不可访问),其他都为保护成员。

私有继承:父类除私有数据成员(不可访问),其他都为私有成员。

语法: class 子类:继承方式 父类

class Father {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
//公共继承
class son1 :public Father {
public:
	void func() {
		m_A = 10;//父类中的公共权限成员,到子类中依然是公共权限
		m_B = 10;//父类中的保护权限成员,到子类中依然是保护权限
		//m_C = 10;//不可访问父类私有成员
	}
};
//保护继承
class son2 :protected Father {
public:
	void func2() {
		m_A = 10;
		m_B = 10;
	}
};
//私有继承
class son3 :private Father {
public:
	void func3() {
		m_A = 100;
		m_B = 100;
	}
};
int main() {
	son1 s1;
	s1.m_A = 100;
	//s1.m_B = 100;不可访问
	son2 s2;
	//s2.m_A; s2.m_B;变为保护权限,类外不可访问
	son3 s3;
	//父类中的保护成员和公有成员均变为了私有成员
}

9.1.4 继承中的对象模型

从父类继承过来的成员,那些属于子类对象中?

class Base {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;//私有成员只是隐藏了但是还是会继承下去
};
class Son :public Base {
public:
	int m_D;
	//父类中所有非静态成员属性都会被子类继承下去
};
int main() {
	cout << "sizeof Son = " << sizeof(Son) << endl;
}
//利用开发人员命令提示工具产看对象模型
//跳转盘符 D:
//跳转文件路径 cd 具体路径下
//cl /d1 reportSingleClassLayout类名 文件名

9.1.5 继承中的构造函数和析构函数

class Base {
public:
	Base() {
		cout << "Base的构造函数!" << endl;
	}
	~Base() {
		cout << "Base的析构函数!" << endl;
	}
};
class Son :public Base {
public:
	Son() {
		cout << "Son的构造函数!" << endl;
	}
	~Son() {
		cout << "Son的析构函数!" << endl;
	}
};
int main() {
	Son s1;
    //先构造父类,再构造子类,析构的顺序与构造的顺序相反
}

9.1.6 继承同名成员处理方式

  • 访问子类同名成员 直接访问即可

  • 访问父类同名成员 需要加作用域运算符

class Base {
public:
	void func() {
		cout << "Base的成员函数" << endl;
	}
	void func(int i) {
		cout << "Base的成员函数" << endl;
	}
};
class Son : public Base {
public:
	void func() {
		cout << "Son的成员函数" << endl;
	}

};
int main() {
	Son s1;
	s1.func();//直接调用 调用是子类中的同名成员
	s1.Base::func();//调用父类中的同名成员需要加作用域
	//s1.func(100);
	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有同名成员函数
	//想访问父类被隐藏的同名成员函数,加作用域
	s1.Base::func(100);
}

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

class Base {
public:
	static int m_A;
};
class Son :public Base {
public:
	static int m_A;
};
int Base::m_A = 100;
int Son::m_A = 200;
int main() {
	Son s1;
	cout << "Son::m_A = " << s1.m_A << endl;
	cout << "Base::m_A = " << s1.Base::m_A << endl;
}

9.1.8 多继承

class Base1 {
public:
	Base1() {
		cout << "Base1的构造函数" << endl;
	}
	~Base1() {
		cout << "~Base1的析构函数" << endl;
	}
};
class Base2 {
public:
	Base2() {
		cout << "Base2的构造函数" << endl;
	}
	~Base2() {
		cout << "~Base2的析构函数" << endl;
	}
};
class Son:public Base1, public Base2 {
public:
	Son() {
		cout << "Son的构造函数" << endl;
	}
	~Son() {
		cout << "Son的析构函数" << endl;
	}
};
int main() {
	Son s;
}

9.1.9 菱形继承

//菱形继承:
//两个派生类继承同一个基类,又有某个类同时继承两个派生类
class Animal {
public:
	int m_Age;
};
class Sheep :virtual public Animal {};
class Tuo :virtual public Animal {};
class SheepTuo :public Sheep, public Tuo {};
int main() {
	SheepTuo st;//菱形继承是,两个父类有相同的数据,需要加作用域区分
	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 19;
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age =  " << st.Tuo::m_Age << endl;
	//这份数据应该有一份
}

10 多态

多态分为两类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名

  • 动态多态:派生类和虚函数实现运行时多态

区别

  • 静态多态的函数地址早绑定,编译阶段确定函数地址

  • 动态多态的函数地址晚绑定,运行阶段确定函数地址

示例:

class Animal {
public:
	void speak() {//虚函数virtual,编译器在编译时不能确定函数了
		cout << "动物在说话" << endl;
	}
};
class Cat :public Animal {
public:
	void speak() {
		cout << "猫在说话" << endl;
	}
};
//动态多态使用方法,父类的指针指向子类的对象
//地址早绑定
void doSpeak(Animal& animal) {//Animal& animal = cat;
	animal.speak();
}
int main() {
	Cat cat;
	doSpeak(cat);
}
//函数重写:返回值类型 函数名 参数列表 完全一致称为重写

10.1.1 多态的纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以及那个虚函数改为纯虚函数:

virtual 返回值类型 函数名 (参数列表) = 0;

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

//有纯虚函数的类为抽象类
//抽象类:1.无法实例化对象 2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base {
public:
	virtual void print() = 0;
};
class Son :public Base {
public:
	virtual void print() {
		cout << "Son中的虚函数" << endl;
	}
};
int main() {
	//Base b;不可创建对象
	Son s;
	s.print();
}

10.1.2 多态案例

class AbstractDrinking {
public:
	virtual void Boil() = 0;
	virtual void Brew() = 0;
	virtual void PourInCup() = 0;
	virtual void PutSomething() = 0;
	void makeDrink() {
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};
class Coffee :public AbstractDrinking {
public:
	virtual void Boil() {
		cout << "煮农夫山泉" << endl;
	}
	virtual void Brew() {
		cout << "冲泡咖啡" << endl;
	}
	virtual void PourInCup() {
		cout << "倒入杯中" << endl;
	}
	virtual void PutSomething() {
		cout << "加入牛奶和糖" << endl;
	}
};
class Tea :public AbstractDrinking {
public:
	virtual void Boil() {
		cout << "煮农夫山泉" << endl;
	}
	virtual void Brew() {
		cout << "冲泡茶叶" << endl;
	}
	virtual void PourInCup() {
		cout << "倒入杯中" << endl;
	}
	virtual void PutSomething() {
		cout << "加入枸杞" << endl;
	}
};
void doWork(AbstractDrinking *abs) {
	abs->makeDrink();//一个接口不同形态
	delete abs;//释放
}
void test01() {
	//制作咖啡
	doWork(new Coffee);
	cout << "-------------------" << endl;
	doWork(new Tea);
}
int main() {
	test01();
	return 0;
}

10.2 虚析构和纯虚析构

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

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

class Animal {
public:
	virtual void speak() = 0;
	Animal() {
		cout << "Animal的构造函数调用" << endl;
	}
	virtual ~Animal() {//虚析构
		cout << "~Animal的析构函数调用" << endl;
	}
	//纯虚析构:virtual ~Animal() = 0;
};
//Animal::~Animal() {纯虚析构
//}
class Cat :public Animal {
public:
	Cat(string name) {
		cout << "Cat的构造函数的调用" << endl;
		m_Name = new string(name);
	}
	//虚析构
	virtual void speak() {
		cout << *m_Name << "小猫在说话" << endl;
	}
	~Cat() {
		if (m_Name != NULL) {
			cout << "Cat的析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
	string *m_Name;
};
void test01() {
	Animal* animal = new Cat("Tom");
	animal->speak();
	//父类指针在析构时候,不会调用子类中的析构函数,导致子类中的析构不会调用,指向子类对象
	delete animal;
}
int main() {
	test01();
}
  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类

11 文件操作

​ 程序运行是产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化

C++对文件操作需要包含头文件

文件类型分为两种:

  • 文本文件:文件以文本的ASCll码形式存储在计算机中
  • 二进制文件:文件一文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的方式:

  1. ofstream:写操作
  2. ifstream:读操作
  3. fstream:读写操作

11.1 文本文件

11.1.1 写文件

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

文件打开方式:

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件夹
ios::app 追加方式写文件
iOS::trunc 如果文件存在先删除,在创建
ios::binary 二进制方式
#include
#include
using namespace std;
void test01() {
	ofstream ofs;
	ofs.open("test.txt", ios::out);
	ofs << "姓名:王二狗" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:20" << endl;
	ofs.close();
}
int main() {
	test01();
}

11.1.2 读文件

  1. 包含头文件:#include
  2. 创建流对象:ifstream ifs;
  3. 打开文件并判断文件是否成功打开:ifs.open(“文件路径”,打开方式);
  4. 读数据:四种方式读取
  5. 关闭文件:ifs.close();
#include
#include
#include
using namespace std;
void test() {
	ifstream ifs;
	ifs.open("test.txt",ios::in);
	if (!ifs.is_open()) {
		cout << "文件打开失败!" << endl;
		return;
	}
	//读数据
	//1.
	char buf[1024] = { 0 };
	while (ifs >> buf) {
		cout << buf << endl;
	}
	//2.
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf,sizeof(buf))) {
	//	cout << buf << endl;
	//}
	//3.
	//string buf;
	//while (getline(ifs, buf)) {
	//	cout << buf << endl;
	//}
	//4.效率低
	//char c;
	//while ( (c = ifs.get()) != EOF) {//EOF end of file
	//	cout << c;
	//}
	ifs.close();
}
int main() {
	test();
}

12 模版

C++另一种编程思想:泛型编程,主要利用的技术就是模版

12.1 模版的概念

​ 模版就是建立通用的模具,大大提高复用性

​ 注:1.不可以直接使用,它只是一个框架

​ 2.模版的通用并不是万能的

12.2 函数模版语法

作用:建立一个通用函数,器函数返回值和形参类型可以不具体制定,用一个虚拟的类型来代表

temlpate
函数声明或定义

template --> 声明创建模版

typename --> 表面其后面的符号是一种数据类型,可以用class代替

T --> 通用数据类型,名称可以替换,通常为大写字母

template
//声明一个模版,告诉编译器后面代码紧跟着的T不要报错,T是一个通用的模版
void mySwap(T &a, T &b) {
	T temp = a;
	a = b;
	b = temp;
}
void main() {
	int a = 10;
	int b = 20;
	//1.自动类型推导
	mySwap(a, b);
	//2.显示制定类型
	mySwap(a, b);
	cout << "a = " << a << ",b = " << b << endl;
}

注意事项:

  1. 自动推导,必须推导出一致的数据类型T才可以使用

  2. 模版必须要确定出T的数据类型,才可以使用

template
void mySwap(T &a, T &b) {
	T temp = a;
	a = b;
	b = temp;
}
template
void func() {
	cout << "func函数调用" << endl;
}
void main() {	
	int a = 1;
	int b = 2;
	char c = 3;
	//mySwap(a, c);错误!推导不出一致的T类型
	//func();模版必须要确定出T的数据类型,才可以使用
    func();
}

你可能感兴趣的:(C++,面向对象编程)