C++ 02-核心编程

(注意:本阶段,主要分析 C++ 面向对象编程技术。)

一、内存分区模型
1、C++ 程序在执行时,将内存大致划分为4个区域:
(1)代码区:存放函数体的二进制代码,由操作系统进行管理的。
(2)全局区:存放全局变量和静态变量以及常量。
(3)栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等。
(4)堆区:由程序员分配和释放,若程序员不释放,那么程序结束时,由操作系统回收。

2、内存分四区的意义:
不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。

3、程序运行前:
在程序编译后,生成了 .exe 可执行程序,未执行该程序前,分为两个区域:
(1)代码区:存放 CPU 可执行的二进制机器指令。
代码区是“共享的”,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
代码区是“只读的”,使其只读的原因,是防止程序意外地修改了它的指令。
(2)全局区:存放全局变量、静态变量。 —> 即:在函数体外的变量
全局区还包含了常量区、字符串常量、其它常量。该区域的数据在程序结束后,由操作系统释放。
(3)总结:
<1> C++中在程序运行前分为:全局区、代码区。
<2> 代码区特点是:共享、只读。
<3> 全局区中存放:全局变量、静态变量、常量。
<4> 常量区中存放:const 修饰的全局常量、字符串常量。

4、程序运行后:
(1)栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等。
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
(2)堆区:由程序员分配和释放,若程序员不释放,那么程序结束时,由操作系统回收。
注意:在C++中,主要利用new关键字,在堆区开辟内存。
示例:

int * func(){
	// 利用new关键字,可以将数据开辟到堆区
	int * p = new int(10);     // 指针本质也是局部变量,放在栈区的,而指针保存的数据是放在堆区的。
	return p;
}
int main(){
	int * p = func();   // 在堆区开辟数据
	cout << *p << endl;
	system("pause");
	return 0;
}

5、new 关键字:
C++中利用new关键字,在堆区开辟数据。堆区开辟的数据是由程序员手动开辟和释放的,释放是利用delete关键字。
(1)语法:new 数据类型; (注:利用new关键字创建的数据,会返回该数据对应数据类型的指针。)
示例:

// 在堆区创建数据
void test01(){
	int * p = new int(10);   // new创建的数据,返回该数据对应数据类型的指针
	cout << *p << endl;
	delete p;   // 由程序员释放该内存,使用delete关键字
	cout << *p << endl;    // 该段内存已经被释放,再次访问是非法操作,会报错!
}
// 在堆区开辟数组
void test02(){
	int * arr = new int[10];   // 在堆区,创建一个数组
	for(int i = 0; i < 10; i++){
		arr[i] = i + 100;   // 给数组赋值
		cout << arr[i] << endl;
	}
	delete[] arr;   // 释放数组,需要加[] 
}

二、引用
1、基本概念
(1)作用:给变量起别名。
(2)语法:数据类型 &别名 = 原名;
示例:

int main(){
	int a = 10;
	int &b = a;     // 创建引用 
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	
	system("pause");
	return 0; 
}

(3)注意事项:
<1> 引用必须要初始化!(例:int &b; // 这是错误的!)
<2> 引用一旦初始化后,就不可以更改了!

2、引用做函数参数
作用:函数传参时,可以利用引用的技术,让形参修改实参。
优点:可以简化指针修改实参。
示例:

// 1.值传递    --> 形参不会修改实参
void swap01(int a, int b){
	int temp = a;
	a = b;
	b = temp;
}
// 2.地址传递   --> 形参会修改实参
void swap02(int *a, int *b){
	int temp = *a;
	*a = *b;
	*b = temp;
}
// 3.引用传递   --> 形参会修改实参
void swap03(int &a, int &b){
	int temp = a;
	a = b; 
	b = temp;
}

3、引用做函数返回值 (注:不要返回局部变量的引用!)
用法:函数调用作为左值。
示例:

// 返回局部变量引用
int& test01(){
	int a = 10;    // 局部变量
	return a;
}
// 返回静态变量引用
int& test02(){
	static int a = 20;     // 静态变量,存放在全局区,上面的数据在程序结束后,由系统释放
	return a;
}

int main(){
	int &ref1 = test01();
	cout << "ref1 = " << ref1 << endl;   // 第一次输出是正确的,是因为编译器做了保留
	cout << "ref1 = " << ref1 << endl;   // 第二次输出是错误的,是因为a的内存已经被释放了

	int &ref2 = test02();
	cout << "ref2 = " << ref2 << endl;   
	cout << "ref2 = " << ref2 << endl;   // 第二次输出也是正确的,是因为a的内存没有被释放

	test02() = 1000;     // 函数调用可以作为左值,来进行赋值操作
	cout << "ref2 = " << ref2 << endl; 
	
	system("pasue");
	return 0;
}

4、引用的本质:引用在C++内部的实现,是一个指针常量(指针指向不可变,指针指向的值可以改变)
示例:

// 发现是引用,转换为:int * const ref = &a;
void func(int& ref){
	ref = 100;    // ref是引用,转换为:*ref = 100
}
int main(){
	int a = 10;
	int & ref = a;    // 自动转换为:int * const ref = &a; 指针常量是指针指向不可改变,这就是引用一旦初始化后不可更改的原因
	ref = 20;     // 内部发现ref是引用,自动帮我们转换为:*ref = 20;
	cout << "a:" << endl;
	cout << "ref:" << endl;
	func(a);
	return 0;
}

总结:C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作,编译器都帮我们做了。

5、常量引用:主要是用来修饰形参,防止误操作的
在函数形参列表中,可以加const修饰形参,防止形参改变实参。
示例:

// 引用使用的场景,通常用来修饰形参
void showValue(const int& val){
	val = 100;    // 不可以修改,报错!
	cout << "val = " << val << endl;
}

int main(){
	int a = 10;
	const int & ref = 10;        // 加上const后,编译器将代码修改为:int temp = 10; const int & ref = temp;  此时值不可修改
	showValue(a);
	
	system("pause");
	return 0;
}

三、函数提高
1、函数的默认参数
在C++中,函数形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名(参数 = 默认值) { };
示例:

int func1(int a, int b = 10, int c = 10){   // 若某个位置参数有默认值,那么从这个位置之后,必须都要有默认值。
	return a + b + c;
}

int func2(int a = 10, int b = 10);    // 若函数声明有默认值,函数实现时就不能有默认参数。
int func2(int a, int b){
	return a + b;
}

int main(){
	cout << func1(10) << endl;     // 因为该函数的形参列表中后面两个形参都有默认参数,所以只需要传递一个参数即可,即表示传参:a = 10
	system("pause");
	return 0;
}

2、函数占位参数
C++中函数的形参列表中可以有占位参数,用来做占位,调用函数时,必须填补该占位。
语法:返回值类型 函数名(数据类型) { };
示例:

void func(int a, int){     // 此处形参列表中,第二个数据类型就是占位参数,占位参数也可以有默认参数
	cout << "this is func" << endl; 
}

int main(){
	func(10,10);    // 此处必须填补占位参数
	system("pause");
	return 0;
}

3、函数重载:函数名可以相同,提高复用性
(1)函数重载满足条件:
1. 同一个作用域下
2. 函数名相同
3. 函数参数的类型、个数、顺序任意不同
注:函数的返回值不可以作为函数重载的条件。
(2) 注意事项:
1. 引用作为函数重载的条件
2. 函数重载碰到默认参数,会出现歧义,所以想用函数重载,就尽量避免默认参数。

四、类和对象
C++面向对象的三大特性为:封装、继承、多态。C++认为万事万物皆为对象,对象上有其属性和行为。
具有相同性质的对象,我们可以抽象称为:类。
1、封装
(1)意义:
<1> 设计一个类时,将属性和行为作为一个整体,表现一个事物。
语法:class 类名{ 访问权限:属性/行为 };
示例:

class Circle{
public:   // 访问权限
	int m_r;  // 半径(属性)
	double calculateZC(){       // 获取圆的周长(行为)
		return 2 * PI * m_r;
	}
};

int main(){
	Circle c1;      // 通过圆类,创建具体的圆对象 (即:实例化)
	c1.m_r = 10;    // 给圆对象的属性进行赋值
	cout << "圆的周长为:" << c1.calculateZC() << endl;     // 调用类中的行为,计算圆的周长
	system("pause");
	return 0;
}

<2> 设计一个类时,可以把属性和行为放在不同的权限下,加以控制。
三种访问权限:
public --> 公共权限(成员在类内、类外均可以被访问)
protected --> 保护权限(成员在类内可以被访问,但在类外不可以被访问)
private --> 私有权限(成员在类内可以被访问,但在类外不可以被访问)

(2)struct 和 class 的区别:
在C++中,他们唯一的区别是:默认访问权限不同( struct:默认为public;class:默认为private)。

(3)成员属性设置为 private
优点:可以自己控制读写权限。对于写权限,我们可以检测数据的有效性。
示例:

class Person{
public:
	void setName(string name){    // 设置属性内容
		m_Name = name;
	}
	void getName(){       // 获取属性内容
		return m_Name;
	}

private:              // 成员属性都设置为私有的
	string m_Name;
	int m_Age;
};

int main(){
	Person p;
	p.setName("张三");
	cout << "姓名为:" << p.getName << endl;
	system("pause");
	return 0;
}

注:在一个类中,可以让另一个类的对象作为这个类的成员变量。

(4)将不同类写在不同的文件中,并将其关联
<1> 首先编写一个 point.h 头文件,示例: —> 在头文件中写声明

#pragma once      // 为了防止头文件重复
#include <iostream>     // 提供标准的文件输入输出
using namespace std;    // 标准的命名空间

class Point{      // 点类
public:
	void setX(int x);   // 头文件中的类,成员变量和函数只需要写出声明,不需要写实现
	int getX();
	void setY(int y);   
	int getY();

private:
	int m_X;
	int m_Y;
}

<2> 再编写一个 point.cpp 的源文件,示例: —> 在源文件中写实现

#include “point.h”    // 表示包含前面编写的头文件

void Point::setX(int x){   // 源文件中直接写所包含头文件中类的实现即可,但是要注明作用域( 例:Point:: )
	m_X = x;
}
int Point::getX(){
	return m_X;
}
void Point::setY(int y){   
	m_Y = y;
}
int Point::getY(){
	return m_Y;
}

<3> 在main()函数的源文件中,包含所需要的类的头文件即可,示例:

#include <iostream>
using namespace std;
#include "circle.h"
#include "point.h"

2、对象的初始化和清理
(1)构造函数和析构函数
对象的初始化和清理,是两个非常重要的安全问题。C++利用了构造函数和析构函数来解决该问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。若我们不提供这两个函数,则编译器会默认提供,编译器提供的构造函数和析构函数是空实现的。
<1> 构造函数:用于创建对象时,为对象的成员属性赋值,构造函数由编译器自动调用。
语法:类名( ) { }
说明:构造函数没有返回值也不写void;函数名与类名相同;可以有参数,因此可以发生重载;程序在调用对象时会自动调用,并且只会调用一次。
<2> 析构函数:用于对象销毁前,系统自动调用,执行一些清理工作。
语法:~类名( ) { }
说明:析构函数没有返回值也不写void;函数名与类名相同,在类名前面加上符号~;不可以有参数,因此不可以发生重载;程序在调用对象时会自动调用,并且只会调用一次。
示例:

class Person{
public:
	// 构造函数
	Person(){
		cout << "Person的构造函数调用" << endl;
	}
	// 析构函数
	~Person(){
		cout << "Person的析构函数调用" << endl;
	}
};

(2)构造函数的分类和利用
<1> 两种分类方式:
按参数分类:有参构造、无参构造(默认构造)
按类型分类:普通构造、拷贝构造

class Person{
public:
	// 普通构造函数
	Person(){
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a){
		cout << "Person的构造函数调用" << endl;
	}
	// 拷贝构造函数
	Person(const Person &p){
		age = p.age;     // 将传入类中的所有属性,拷贝过来
		cout << "Person的拷贝构造函数调用" << endl;
	}
};
<2> 三种调用方式:
	括号法、显示法、隐式转换法
	示例:
void test(){
	// 1.括号法  --> 最常用
	Person p1;      // 默认构造函数的调用,注意:调用无参构造函数时,不要加()!因为编译器会把`Person p1(); `误认为是函数声明。 
	Person p2(10);   // 有参构造函数的调用
	Person p3(p2);    // 拷贝构造函数的调用
	Person(10);     // 匿名对象,特点:当前执行结束后,系统会立即回收掉匿名对象。不要利用拷贝构造函数来初始化一个匿名对象!
	// 2.显示法
	Person p4 = Person(10);
	Person p5 = Person(p4);
	// 3.隐式转换法
	Person p5 = 10;
	Person p6 = p4;
}

(3)拷贝构造函数调用时机
C++中拷贝构造函数调用时机,通常有三种情况:
<1> 使用一个已经创建完毕的对象来初始化一个新对象
<2> 值传递的方式给函数参数传值
<3> 以值的方式返回局部对象
示例:

// 创建一个Person类
class Person{
public:
	Person(){
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	Person(int age){
		cout << "有参构造函数!" << endl;
		mAge = age
	}
	Person(const Person & p){
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}
};
// 1.使用一个已经创建完毕的对象,来初始化一个新对象
void test01(){
	Person p1(20);
	Person p2(p1);
	cout << "p2的年龄为:" << p2.mAge << endl;
}
// 2.以值传递的方式,给函数的参数传值
void doWork(Person p){    // 会拷贝一个新的Person对象,不会修改原始对象
	
}
void test02(){
	Person p;
	doWork(p);
}
// 3.以值的方式返回局部对象
void doWork2(){    
	Person p1;
	return p1;
}
void test03(){
	Person p = doWork2();
}

(4)构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数:
1.默认构造函数(无参);2.默认析构函数(无参);3.默认拷贝构造函数(对属性进行值拷贝)
构造函数调用规则如下:
<1> 若用户定义了有参构造函数,则C++不再提供默认的无参构造函数,但是会提供默认拷贝构造函数
<2> 若用户定义了拷贝构造函数,则C++不再提供其它构造函数

(5)浅拷贝和深拷贝
<1> 浅拷贝:就是简单的赋值拷贝操作(存在的问题:堆区的内存重复释放)
<2> 深拷贝:在堆区重新申请空间,进行拷贝操作(堆区开辟的数据,在析构函数中进行释放操作)
注:若属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

(6)初始化列表
作用:C++提供了初始化列表的语法,用来初始化属性
语法:构造函数( ):属性1(值1), 属性2(值2), ...{ };
示例:

class Person{
public:
	// 传统初始化操作
	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){
	}

	int m_A;
	int m_B;
	int m_C;
};

(7)静态成员 —> 就是在成员变量、成员函数前加上static关键字
<1> 静态成员变量
1.所有对象共享同一份数据
2.在编译阶段分配内存
3.类内声明,类外初始化
<2> 静态成员函数
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
示例:

class Person{
public:     // --> 静态成员函数也是可以设置访问权限的(类外无法访问私有的静态成员函数)
	// 静态成员函数     
	static void func(){
		m_A = 100;    // 静态成员函数可以访问静态成员变量
		cout << "静态成员函数的调用" << endl;
	}
	// 静态成员变量
	static int m_A;   
};

int Person::m_A = 0;    // 通过类名作用域,访问静态成员变量

// 静态成员函数有两种访问方式
void test(){
	// 1.通过对象进行访问
	Person p;
	p.func();
	// 2.通过类名进行访问
	Person::func();
}

3、C++对象模型和 this 指针
(1)成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上。
(说明:空对象占用1个字节的内存空间,因为C++编译器会给每个空对象分配一个字节空间,为了区分不同的空对象占用内存空间的位置,每个空对象都有一个独一无二的内存地址。)

(2)this 指针概念
this指针指向被调用的成员函数所属的对象。this指针是隐含在每一个非静态成员函数内的一种指针。
作用:当形参和成员变量同名时,可用this指针来区分;在类的非静态成员函数中,需要返回对象本身,可使用:return * this
示例:

class Person(){
public:
	// 构造函数
	Person(int age){
		this->age = age;    // this指针,指向被调用的成员函数所属的对象
	}
	// 成员函数
	Person& PersonAddAge(Person &p){   // 若返回值是对象本体,那么返回值类型就要用引用的方式
		this->age += p.age;
		return *this;    // this指向的是p2的指针,*this指向的就是p2这个对象本体
	}
	// 成员变量
	int age;
};
// 1.this指针解决名称冲突
void test01(){
	Person p1(18);
	cout << "p1对象的年龄:" << p1.age << endl;
}
// 2.使用*this,返回对象本身
void test02(){
	Person p1(10);
	Person p1(10);
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);  // 由于该函数返回值是对象,因此可以多次调用(链式编程思想)
	cout << "p2的年龄为:" << p2.age << endl;
}

(3)空指针访问成员函数
C++中空指针可以调用成员函数,但是要注意有没有用到this指针,若用到this指针,则需要进行判断,以保证代码的健壮性。
示例:

class Person{
public:
	void showClassName(){
		cout << "这是Person类!" << endl;
	}
	void showPersonAge(){
		if(this == NULL){    // 判断该指针是否为空
			return;
		}
		cout << "age = " << m_Age << endl;
	}
	int m_Age;	
};
void test(){
	Person * p = NULL;
	p->showClassName();
	p->showPersonAge(); 
}

(4)const修饰成员函数
<1> 常函数
1.成员函数后面加 const关键字后,称为常函数。
2.常函数内不可以修改成员属性。
3.成员属性声明时,加 mutable关键字后,在常函数中依然可以修改。
<2> 常对象
1.声明对象前面加 const关键字后,称为常对象。
2.常对象只能调用常函数。
示例:

class Person{
public:
	void showPerson() const{   // 表示为常函数
		m_A = 100;
	}
	int m_A;
	mutable int m_B;   // 表示为一个特殊的变量,此时在常函数中,可以对其进行修改
};
void test(){
	const Person p;    // 表示p为一个常对象
	p.m_A =1000;    // 此时会报错!因为常对象不能调用普通成员
	p.m_B =1000;    //  加了mutable关键字的变量,可以被常对象调用
	p.showPerson();    // 常对象只能调用常函数,不能调用普通成员函数
}

(5)友元
作用:让一个函数或类访问另一个类中的私有成员。(关键字:friend)
三种实现:
<1> 全局函数做友元
示例:

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

<2> 类做友元
示例:

class Building;     // 类的声明

class GoodGay{
public:
	GoodGay();
public:
	void visit();    // 成员函数,访问Building类中的属性
	Building * building;
};

class Building{
	friend class GoodGay;      // 表示GoodGay类是本类的友元类,可以访问本类中的私有成员
public:
	Building();      // 构造函数的声明
public:
	string m_SittingRoom;
private:
	string m_BedRoom;
};

// 类外写构造函数
Building::Building(){class Person{
public:
	void showPerson() const{   // 表示为常函数
		m_A = 100;
	}
	int m_A;
	mutable int m_B;   // 表示为一个特殊的变量,此时在常函数中,可以对其进行修改
};
void test(){
	const Person p;    // 表抽象类示p为一个常对象
	p.m_A =1000;    // 此时会报错!因为常对象不能调用普通成员
	p.m_B =1000;    //  加了mutable关键字的变量,可以被常对象调用
	p.showPerson();    // 常对象只能调用常函数,不能调用普通成员函数
}
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodGay::GoodGay(){
	building = new Building;   // 创建对象
}
void GoodGay::visit(){
	cout << "好基友类,正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友类,正在访问:" << building->m_BedRoom << endl;
};

<3> 成员函数做友元
示例:

class Building;     // 类的声明

class GoodGay{
public:
	GoodGay();
public:
	void visit();    // 让该成员函数,可以访问Building类中的私有成员
	void visit2();    // 让该成员函数,不可以访问Building类中的私有成员
	Building * building;
};

class Building{
	friend void GoodGay::visit();      // 表示GoodGay类中的visit()函数,是本类的友元成员函数,可以访问本类中的私有成员
public:
	Building();
public:
	string m_SittingRoom;
private:
	string m_BedRoom;
};

// 类外实现构造函数
Building::Building(){
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodGay::GoodGay(){
	building = new Building;   // 创建对象
}
// 类外实现成员函数
void GoodGay::visit(){
	cout << "visit()函数,正在访问:" << building->m_SittingRoom << endl;
	cout << "visit()函数,正在访问:" << building->m_BedRoom << endl;
};
void GoodGay::visit2(){
	cout << "visit2()函数,正在访问:" << building->m_SittingRoom << endl;
	cout << "visit2()函数,正在访问:" << building->m_BedRoom << endl;
};

void test(){
	GoodGay gg;
	gg.visit();
	gg.visit2();     // 会报错!visit()不是友元成员函数
}

(6)运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
<1> 加号运算符 + 重载:用于实现两个自定义数据类型相加的运算。
示例:

class Person{
public:
	// 1.利用成员函数,重载+号运算符
	Person operator+(Person &p){
		Person temp;
		temp.m_A = this->m_A + m_A;
		temp.m_B = this->m_B + m_B;
		return temp;
	}
	int m_A;
	int m_B;
};

// 2.利用全局函数,重载+号运算符
Person operator+(Person &p1, Person &p2){
	Person temp;
		temp.m_A = this->m_A + m_A;
		temp.m_B = this->m_B + m_B;
		return temp;
}

void test(){
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	
	Person p3 = p1 + p2;
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
}

注:对于内置数据类型的表达式的运算符是不可以改变的。不要滥用运算符重载!

<2> 左移运算符 << 重载:可以输出自定义数据类型。
示例:

class Person{
public:
	int m_A;
	int m_B;
};

// 利用全局函数,重载<<号运算符
ostream& operator<<(ostream &cout, Person &p){
	cout << "m_A = " << p.m_A << ", m_B = " << p.m_B;
	return cout;
}
	
void test(){
	Person p;
	p.m_A = 10;
	p.m_B = 10;
	cout << p << endl;
}

注:重载左移运算符,配合友元,可以实现输出自定义数据类型。

<3> 递增运算符 ++ 重载:可以实现自己的整型数据。
示例:

// 自定义的整型
class MyInteger{
	friend ostream& operator<<(ostream& cout, 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& cout, MyInteger myint){
	cout << myint.m_Num;
	return cout;
}

void test(){
	MyInteger myint;
	cout << ++myint << endl;
	cout << myint++ << endl;
}

注:前置递增返回的是引用,后置递增返回的是值。

<4> 赋值运算符 = 重载:若类中有属性指向堆区,做赋值操作时,也会出现深浅拷贝的问题。
示例:

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){
		// m_Age = p.m_Age;    // 此时,编译器是提供浅拷贝,会报错!
		// 应该先判断是否有属性在堆区。若有,先释放干净,然后再进行深拷贝。
		if(){
			delete m_Age;
			m_Age = NULL;
		}
		m_Age = new int(*p.m_Age);    // 此时,编译器进行深拷贝
		return *this;    // 返回对象本身
	}

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

<5> 关系运算符 == 重载:可以让两个自定义类型的对象进行对比操作。
示例:

class Person{
public:
	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 true;
		}
		return false;
	}
	string m_Name;
	int m_Age;
};

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

<6> 函数调用运算符()重载:由于重载后的使用方式非常像函数的调用,因此称为 仿函数。它没有固定写法,非常灵活。
示例:

class MyPrint{
public:
	// 函数调用运算符重载
	void operator()(string str){
		cout << str << endl;
	}
};

void test(){
	MyPrint myPrint;
	myPrint("hello world");  // 由于重载后的使用方式非常像函数的调用,因此称为:仿函数。
}

(7)继承:可以减少重复代码
<1> 语法: class 子类名 : 继承方式 父类名{ }; ( 例:class A : public B{ }; ) // 其中子类也叫做派生类,父类也叫做基类
<2> 继承方式:公共继承(public)、保护继承(protected)、私有继承(private)
在这里插入图片描述
说明:
1.父类中的私有成员,子类都无法访问。
2.公共继承中,父类中的公共、保护成员,到子类中依然是公共、保护权限。
3.保护继承中,父类中的公共、保护成员,到子类中都变成保护权限。
4.私有继承中,父类中的公共、保护成员,到子类中都变成私有权限。

<3> 继承中的对象模型
注:父类中所有非静态成员属性都会被子类继承下去,父类中私有成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承下去。

<4> 继承中构造、析构的顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
顺序如下:
1.先构造父类,再构造子类。
2.先析构子类,在析构父类。

<5> 继承同名成员处理方式
当子类与父类之间,出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据:
1.访问子类中的同名成员,直接访问即可。
2.访问父类中的同名成员,需要加作用域。
示例:

// 父类
class Base{
public:
	Base(){
		m_A = 100;
	}
	void func(){
		cout << "Base下的func()函数调用!" << endl;
	}
	void func(int a){
		cout << "Base下的func(int a)函数调用!" << endl;
	}
	int m_A;
};
// 子类
class Son : public Base{
public:
	Son(){
		m_A = 100;
	}
	void func(){
		cout << "Son下的func()函数调用!" << endl;
	}
	int m_A;
}; 
// 1.同名成员变量的处理方式
void test(){
	Son s;
	cout << "Son下的m_A = " << s.m_A << endl;
	cout << "Base下的m_A = " << s.Base::m_A << endl;    // 访问父类中的同名成员,需要加作用域
}
// 2.同名成员函数的处理方式
void test(){
	Son s;
	s.func();
	s.Base::func();       // 访问父类中的同名成员,需要加作用域
	s.Base::func(100);    // 若子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
						  // 此时若想访问父类中被隐藏的同名成员函数,需要加作用域
}

<6> 继承中同名静态成员处理方式
静态成员和非静态成员出现同名的处理方式一致,只不过有两种访问的方式(即:通过对象或类名进行访问)。

<7> 多继承:C++允许一个类继承多个类(一般不推荐使用!)
语法:class 子类 : 继承方式 父类1, 继承方式 父类2, ...{ }; (注:多继承中可能会引发不同父类中的同名成员出现,需要加作用域来区分。)

<8> 菱形继承
概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类,这种继承被成为菱形继承(或钻石继承)。
在这里插入图片描述
产生的问题: —> 利用虚继承的方式,来解决!
1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
2.草泥马继承自动物的数据继承了两份(分别是羊和驼的数据),但是这两份数据我们只需要一份就可以了。
示例:

// 动物类
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 test(){
	SheepTuo st;
	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 28;
	// 菱形继承时,两个父类拥有相同的数据,需要加作用域来区分。
	cout << "羊的年龄:" << st.Sheep::m_Age << endl;
	cout << "驼的年龄:" << st.Tuo::m_Age << endl;
	// 加上虚继承之后,就只有一份数据了
	cout << "羊的年龄:" << st.m_Age << endl;
}

(8)多态
分为两类:
<1> 静态多态:函数重载和运算符重载属于静态多态,复用函数名。
<2> 动态多态:派生类和虚函数实现运行时多态。
( 满足动态多态的条件:1. 有继承关系;2. 子类重写父类的虚函数。)
( 动态多态的使用:父类的指针或引用,指向子类对象。 )
区别:
<1> 静态多态的函数地址早绑定 ——> 在编译阶段就确定了函数地址
<2> 动态多态的函数地址晚邦定 ——> 在运行阶段才确定了函数地址
多态的好处:
<1> 组织结构清晰
<2> 可读性强
<3> 对于前期和后期扩展以及维护性高

(9)纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数的语法:virtual 返回值类型 函数名(参数列表)= 0; // 当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
<1> 抽象类无法实例化对象。
<2> 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

(10)虚析构和纯虚析构
在多态使用时,若类中有属性开辟到堆区,那么父类指针在释放时,无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或纯虚析构。
两者共性:
<1> 可以解决父类指针释放子类对象。
<2> 都需要具体的函数实现。
两者区别:
<1> 若是纯虚析构,则该类属于抽象类,无法实例化对象。
虚析构函数的语法:virtual ~类名( ) { };
纯虚析构函数的语法:virtual ~类名( ) = 0; 类名::~类名( ) { };
总结:
<1> 虚析构函数或纯虚构函数,就是用来解决通过父类指针释放子类对象的问题。
<2> 若子类中没有堆区数据,可以不写为虚析构函数或纯虚析构函数。
<3> 拥有纯虚析构函数的类,也属于抽象类。

4、文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。通过文件可以将数据持久化。
( 注意:C++中对文件操作,需要包含头文件:#include )
文件类型分为两种:
<1> 文本文件:该文件以文本的ASCII码形式存储在计算机中。
<2> 二进制文件:该文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们。
操作文件的三大类:
<1> ofstream:写操作
<2> ifstream:读操作
<3> fstream:读写操作

(1)文本文件
<1> 写文件(步骤)
1.包含头文件: #include
2.创建流对象:ofstream ofs;
3.打开文件:ofs.open(“文件路径”, 打开方式);

(注意:文件打开方式可以配合使用,利用 | 操作符,例如:用二进制方式写文件:ios::binary | ios::out )
4.写数据:ofs << “写入的数据”;
5.关闭文件:ofs.close();

<2> 读文件(步骤)
1.包含头文件: #include
2.创建流对象:ifstream ofs;
3.打开文件,并判断文件是否打开成功:ifs.open(“文件路径”, 打开方式); ( 注:利用is_open()函数,可以判断文件是否打开成功。)
4.读数据:四种方式读取
5.关闭文件:ifs.close();

(2)二进制文件
以二进制的方式对文件进行读写操作时,打开方式要指定为:ios::binary
<1> 写文件
二进制方式写文件,主要利用流对象调用成员函数 write()
函数原型:ostream& write(const char * buffer, int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。

<2> 读文件
二进制方式读文件,主要利用流对象调用成员函数 read()
函数原型:istream& read( char * buffer, int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探索全栈前端技术的魅力:HTML+CSS+JS+JQ+Bootstrap网站源码深度解析 在这个数字化时代,构建一个既美观又功能强大的网站成为了许多开发者和企业追逐的目标。本份资源精心汇集了一套完整网站源码,融合了HTML的骨架搭建、CSS的视觉美化、JavaScript的交互逻辑、jQuery的高效操作以及Bootstrap的响应式设计,全方位揭秘了现代网页开发的精髓。 HTML,作为网页的基础,它构建了信息的框架;CSS则赋予网页生动的外观,让设计创意跃然屏上;JavaScript的加入,使网站拥有了灵动的交互体验;jQuery,作为JavaScript的强力辅助,简化了DOM操作与事件处理,让编码更为高效;而Bootstrap的融入,则确保了网站在不同设备上的完美呈现,响应式设计让访问无界限。 通过这份源码,你将: 学习如何高效组织HTML结构,提升页面加载速度与SEO友好度; 掌握CSS高级技巧,如Flexbox与Grid布局,打造适应各种屏幕的视觉盛宴; 理解JavaScript核心概念,动手实现动画、表单验证等动态效果; 利用jQuery插件快速增强用户体验,实现滑动效果、Ajax请求等; 深入Bootstrap框架,掌握移动优先的开发策略,响应式设计信手拈来。 无论是前端开发新手渴望系统学习,还是资深开发者寻求灵感与实用技巧,这份资源都是不可多得的宝藏。立即深入了解,开启你的全栈前端探索之旅,让每一个网页都成为技术与艺术的完美融合!
探索全栈前端技术的魅力:HTML+CSS+JS+JQ+Bootstrap网站源码深度解析 在这个数字化时代,构建一个既美观又功能强大的网站成为了许多开发者和企业追逐的目标。本份资源精心汇集了一套完整网站源码,融合了HTML的骨架搭建、CSS的视觉美化、JavaScript的交互逻辑、jQuery的高效操作以及Bootstrap的响应式设计,全方位揭秘了现代网页开发的精髓。 HTML,作为网页的基础,它构建了信息的框架;CSS则赋予网页生动的外观,让设计创意跃然屏上;JavaScript的加入,使网站拥有了灵动的交互体验;jQuery,作为JavaScript的强力辅助,简化了DOM操作与事件处理,让编码更为高效;而Bootstrap的融入,则确保了网站在不同设备上的完美呈现,响应式设计让访问无界限。 通过这份源码,你将: 学习如何高效组织HTML结构,提升页面加载速度与SEO友好度; 掌握CSS高级技巧,如Flexbox与Grid布局,打造适应各种屏幕的视觉盛宴; 理解JavaScript核心概念,动手实现动画、表单验证等动态效果; 利用jQuery插件快速增强用户体验,实现滑动效果、Ajax请求等; 深入Bootstrap框架,掌握移动优先的开发策略,响应式设计信手拈来。 无论是前端开发新手渴望系统学习,还是资深开发者寻求灵感与实用技巧,这份资源都是不可多得的宝藏。立即深入了解,开启你的全栈前端探索之旅,让每一个网页都成为技术与艺术的完美融合!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值