IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++核心编程 -> 正文阅读

[C++知识库]C++核心编程

C++核心编程

1、c++内存模型

c++程序执行时将内存大致分为4个区域

  • 代码区:存放CPU执行的二进制代码指令,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及全局常量和字符串常量
  • 栈区:由编译器自动编译释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配释放,如果程序员不释放,则程序结束时由操作系统回收

c++内存模型图如下:
在这里插入图片描述

?

代码区特点

共享的:对于频繁执行的程序,在内存中只要有一份代码即可

只读的:防止系统意外修改它的指令

全局区特点

该区域的数据,在程序结束后由操作系统释放

栈区特点

由编译器自动编译释放,存放函数的参数值,局部变量等

注意事项:栈区编译的数据执行完后由编译器自动释放,不要返回局部变量的地址

堆区特点

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在c++中主要通过new关键字在堆区开辟内存

new 和delete关键字

new关键字开辟内存

elete关键字释放指针p指向的内存

int* func()
{
	//用new关键字开辟内存,new返回的是该数据的指针
	int * p = new int(21);
	return p;
}

int main()
{
	int * p = func();
	cout << *p << endl;
	//使用delete关键字释放指针p指向的内存
	delete p;
	system("pause");
	return 0;
}

2、引用

引用可以看做是变量的一个别名,通过这个别名和原来的名字都能够找到这个变量。

语法:

 //数据类型  &别名 = 原名
 int a = 10;
 int &b = a;

注意事项:引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它变量。

引用作为函数参数

将函数的形参指定为引用的形式,这样在调用函数时就会将实参和形参绑定在一起,让它们都指代同一个变量。如此一来,如果在函数体中修改了形参的值,那么实参的值也会被修改。

//引用传递,&a1,&b1分别是变量a和b的引用
void swap(int &a1,int &b1)
{
	int temp = a1;
	a1 = b1;
	b1 = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << a << "\n" << b << endl;
	system("pause");
	return 0;
}

引用作为函数返回值

不能返回局部数据(例如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在了,C++ 编译器检测到该行为时也会给出警告。

int& test() {
	static int a = 10;//这里把a变成静态变量,存放在全局区,全局区的数据在程序结束后由系统释放
	return a;
}
int main()
{
	int &ref = test();
	cout << ref << endl;  //10
    test() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值
    cout << ref << endl;  //1000
	system("pause");
	return 0;
}

引用的本质

引用的本质是一个指针常量,所有指针操作由编译器完成。

常量引用

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

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

void showValue(const int& a1) {
	// a1+=1000; 不允许修改
    cout << a1 << endl;
}

int main()
{
	int a = 10;
	showValue(a);
	system("pause");
	return 0;
}

3、函数高级

函数默认参数

在c++中函数的形参列表中的参数是可以有默认值的;

在函数中如果某个参数有默认值,那么它后面的所有参数都得有默认参数;

如果函数声明有默认参数,那么函数的定义就不能有默认参数

//如果我们有传入数据,就用传入的数据,没有的话就用默认数据
int func01(int a, int b = 10, int c = 10) {
	return a + b + c;
}

//函数声明,存在默认参数
int func02(int a, int b = 10);
//那么在函数定义时就不能有默认参数
int func02(int a, int b) {
	return a + b;
}

int main()
{
	cout << func01(1, 4) << endl;//15
	cout << func01(1) << endl;  //21
	cout << func02(1) << endl; //11
	system("pause");
	return 0;
}

函数占位参数

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

占位参数还可以有默认值

//占位参数,设置了默认值
void func(int a, int = 5) {
	cout << "This is a function " << endl;
	cout << a << endl;
}

int main() {
    //调用函数时填补占位参数
	func(12, 9);
	system("pause");
	return 0;
}

函数重载

重载:函数名称相同,函数参数类型不同或者个数不同或者顺序不同

void func() {
	cout << "This is a function  01 " << endl;
}

void func(int a) {
	cout << "This is a function  02" << endl;
}
void func(double a) {
	cout << "This is a function  03" << endl;
}

void func(int a,double b) {
	cout << "This is a function  04" << endl;
}
void func(double a, int b) {
	cout << "This is a function  05" << endl;
}

注意事项:

  • 引用可以作为重载条件
  • 重载时避免出现默认参数,会出现二义性

4、面向对象

1、封装

封装

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

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

成员变量和成员函数

class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};

成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。

成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。

权限

类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。

C++ 中的 public、private、protected 只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分。

在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

struct 和 class 的区别

区别:

  • 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的

  • class 继承默认是 private 继承,而 struct 继承默认是 public 继承

  • class 可以使用模板,而 struct 不能

属性私有

优点:

  1. 将所有成员属性设置为私有,可以自己控制读写权限
  2. 对于写权限,我们可以检测数据的有效性

2、对象初始化和清理

构造函数

主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用

class Person {
public:
	int m_age;
	Person() {
		cout << "Person的无参构造" << endl;
	}
	Person(string name,int age) {
		 m_age = age;
		cout << "Person的有参构造" << endl;
	}
    	Person(const Person &p) {
		 m_age = p.m_age;
		cout << "Person的拷贝构造" << endl;
	}

};

析构函数

主要作用在于对象销毁前由系统自动调用,执行一些清理工作,

程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次

class Person {
public:
	int m_age;
	~Person() {
		cout << "Person的析构函数" << endl;
	}
};

深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝

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

浅拷贝带来的问题:堆区的内存重复释放

深拷贝解决:自己提供拷贝构造函数,给拷贝的副本重新开辟内存空间

Person(const Person &p) {
		m_age = p.m_age;
		m_Height = new int(*p.m_Height);
	}

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

构造和析构函数的调用

#include<iostream>
using namespace std;

//分类
class Person {
public:
	int m_age;
	Person() {
		cout << "Person的无参构造" << endl;
	}
	Person(int age) {
		m_age = age;
		cout << "Person的有参构造" << endl;
	}
	Person(const Person& p) {
		m_age = p.m_age;
		cout << "Person的拷贝构造" << endl;
	}
	~Person() {
		cout << "Person的析构函数" << endl;
	}
};
//调用
void test01() {
	//1.括号法
	Person p1;
	Person p2(30);
	Person p3(p2);//调用拷贝构造

	cout << "p2的年龄" << p2.m_age << endl;
	cout << "p3的年龄" << p3.m_age << endl;

	//2.显示法
	Person p4;
	Person p5 = Person(10);
	Person p6 = Person(p5);

	cout << "p5的年龄" << p5.m_age << endl;
	cout << "p6的年龄" << p6.m_age << endl;
	Person(10);//匿名对象 当前行执行结束,系统会立即回收匿名对象,不要用拷贝函数初始化匿名对象


	//3.隐式转换法
	//等价于 Person p7 = Person(p6)
	Person p7 = p6;
	//等价于 Person p8 = Person(10)
	Person p8 = 10;
}

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

拷贝函数使用场景

1、使用一个已创建的对象来初始化一个新的对象

Person p1;
Person p3(p1);

2、值传递的方式来给函数参数传值

//形参是实参拷贝的副本
void doWork(Person p) {
	
}
void test02() {

	Person p1;
    //值传递的方式来给函数参数传值
	doWork(p1);
}

3、以值方式返回局部对象

//返回的对象是局部对象拷贝的副本
Person doWork2() {
	Person p2;
	return p2;
}

void test03() {
	Person p3 = doWork2();
}

构造函数调用规则

默认情况下c++构造器至少给一个类添加3个函数

  • 默认构造函数(无参,函数体为空)

  • 默认析构函数(无参,函数体为空)

  • 默认拷贝构造函数,对属性进行值拷贝

构造函数的调用规则如下:

  • 如果用户定义有参构造,c++不再提供默认无参构造,但还是会提供默认拷贝构造函数
  • 如果用户提供拷贝构造函数,c++不会再提供其他构造函数

初始化列表

初始化列表:用来初始化属性

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	int height;
	int weight;

	//传统初始化属性
	/*P(int a, int b, int c){
	   m_A = a;
	   m_B = b;
	   m_C = c;
	}*/
    
	//初始化列表
	Person(int a, int b, int c) :age(a), height(b), weight(c) 
    {
	}
};

void test01() {
	Person p(18, 180, 130);
	cout << p.age << endl;
	cout << p.height << endl;
	cout << p.weight << endl;
}

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

类对象作为类成员

C++类中的成员允许是另一个类的对象,我们称该成员为:对象成员

当其他类的对象作为本类的成员:

  • 在构造中,是先构造其他类的,在构造本类的。
  • 在析构中,是先析构本类,再析构他类。

静态成员

静态成员变量:

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

静态成员函数:

  1. 所有对象共享同一个函数
  2. 静态成员函数只能访问静态成员函数
  3. 静态成员函数可以访问静态成员变量,不能访问非静态成员变量

静态成员访问方式:

  1. 通过对象进行访问
  2. 通过类名进行访问

3、c++对象模型和this指针

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

在c++中类内的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象

#include<iostream>
using namespace std;

//空对象
class Person {

};
class Demo {
	int m_A;//非静态成员变量属于对象上
};
class Student {
	//非静态成员函数不在对象上
	void sayHello() {

	}
};

class Work {
	//静态变量不在对象上
	static int a;
	static void sayHello() {
		cout << "你好呀" << endl;
	}
};
//类外初始化静态变量
int Work::a = 10;
void test01() {
	Person p;
	//在c++中,为了区别不同的空对象,编译器为其赋了一个字节的存储空间
	cout << sizeof(p) << endl;//1  空对象

	Demo d;
	cout << sizeof(d) << endl;//4   非静态成员变量属于对象上

	Student s;//空对象
	cout << sizeof(s) << endl;//1   非静态成员函数不在对象上

	Work w;//空对象
	cout << sizeof(w) << endl;//1  静态变量不在对象上
}
int main() {
	test01();
	system("pause");
	return 0;
}

this指针

this 是 C++中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	Person(int age) {
		//this指向成员变量或成员函数被调用的对象
		this->age = age;
	}
	//如果返回的是一个引用,则返回的是本身
	Person& personAddAge(Person& person) {
		this->age += person.age;
		//this指向p2的指针,而*this指向p2这个对象本体
		return *this;
	}
	//如果返回的是一个对象,则相当于创建了一个新的对象
	Person personAddAge1(Person& person) {
		this->age += person.age;
		return *this;
	}
};

void test01() {
	Person p(18);
	cout << "p的年龄为" << p.age << endl;
}

void test02() {
	Person p1(10);
	Person p2(10);
	Person p3(10);
	p2.personAddAge(p1).personAddAge(p1).personAddAge(p1);
	p3.personAddAge1(p1).personAddAge1(p1).personAddAge1(p1);
	cout << "p2的年龄为:" << p2.age << endl;//40
	cout << "p3的年龄为:" << p3.age << endl;//20
}
int main() {

	test01();//18
	test02();
	system("pause");
	return 0;
}

注意事项:

  • this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
  • this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
  • 只有当对象被创建后 this 才有意义,因此不能在静态成员函数中使用。

空指针访问成员函数

c++中空指针也是可以调用this指针的,但是也要注意有没有用到this指针

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

#include<iostream>
using namespace std;

class Person {
public:
	int m_Age;
	void showClass() {
		cout << "这是小学一年级" << endl;
	}
	void showAge() {
		//对this做判空处理,增强代码的健壮性,防止空指针带来的异常
		if (this == NULL) {
			cout << "空指针"<< endl;
			return;
		}
		cout << "你的年龄" << this->m_Age << endl;
	}
};
void test01() {
	//创建一个空指针
	Person* p = NULL;
	p->showClass();//空指针可以调用成员函数
	p->showAge();//由于空指针直接调用导致程序崩溃 解决方案,在成员函数中,对this多加一层判断
}
int main() {
	test01();
	system("pause");
	return 0;
}

const修饰成员函数

常函数:

  1. 成员函数后加上const后我们称这个函数为常函数
  2. 常函数内不可以修改成员属性
  3. 只有在成员属性声明时加上关键字mutable,才可以在常函数内进行修改

常对象:

  1. 声明对象前加const,称声明对象为常对象
  2. 常对象只能调用常函数
#include<iostream>
using namespace std;
class P {
public:
	int m_A;
	mutable int m_B; // mutable 修饰的成员属性

	//常函数
	void changeValue() const {
		//this->m_A = 200;  常函数内不可以修改成员属性
		m_B = 20;// mutable 修饰的成员属性可以在常函数进行修改
	}

	void func() {
	}
};

void test01() {
	P p;
	p.changeValue();
}
void test02() {
	//在对象前加上关键字const修饰,变为常对象
	const P p;
	//p.m_A = 20;
	p.m_B = 30;//被mutable修饰的成员属性,在常对象下也可以进行修改
	//p.func(); //常对象不可以调用普通的成员函数,因为普通的成员函数可以修改成员属性
	p.changeValue();//常对象只能调用常函数
}
int main() {

	test01();
	test02();
	system("pause");
	return 0;
}

4、友元

在 C++中,一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。

但是借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。

友元函数:在函数前面加 friend 关键字,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。

友元的三种实现:

1、全局函数声明为友元函数

#include <iostream>
using namespace std;

class Building {
	//将全局函数goodFriend()声明为友元函数,就可以访问私有成员
	friend void goodFriend(Building* b);
public:
	Building() {
		m_LivingRoom = "客厅";
		m_BedRoom = "卧室";
	}
public:
	string m_LivingRoom;
private:
	string m_BedRoom;
};
//全局函数
void goodFriend(Building* b) {
	cout << "好朋友正在访问" << b->m_LivingRoom << endl;
	cout << "好朋友正在访问" << b->m_BedRoom << endl;  //访问私有成员
}

void test01() {
	Building b;
	goodFriend(&b);
}
int main() {

	test01();
	system("pause");
	return 0;
}

2、类做友元

不仅可以将一个函数声明为一个类的“朋友”,还可以将整个类声明为另一个类的“朋友”,这就是友元类。友元类中的所有成员函数都是另外一个类的友元函数。

例如将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性的。

#include <iostream>
using namespace std;
//类做友元

class Building {
	//GoodFriend声明为Building的友元类,可以访问Building的所有成员
	friend class GoodFriend;
public:
	Building();//声明构造函数,类外实现

public:
	string m_LivingRoom;
private:
	string m_BedRoom;
};

class GoodFriend {
public:
	GoodFriend();
	void visit(); //用来访问Building的成员
	Building * b;
};

//类外实现构造函数
Building::Building() {
	m_LivingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodFriend::GoodFriend() {
	b = new Building;
}
void GoodFriend::visit() {
	cout << "好朋友正在访问" << b->m_LivingRoom << endl;
	cout << "好朋友正在访问" << b->m_BedRoom << endl;
}

int main() {

	GoodFriend g;
	g.visit();
	system("pause");
	return 0;
}

3、将其他类的成员函数声明为友元函数

#include <iostream>
using namespace std;

class Building; //提前声明Building类

//声明GoodFriend类
class GoodFriend {
public:
	GoodFriend();
	void visit(); //参观函数,用来访问Building的成员
	void visit2(); //参观函数2,不能访问Building的私有成员
private:
	Building* b;
};
//声明Building类
class Building {
	//将GoodFriend类中的成员函数visit()声明为友元函数
	friend void GoodFriend::visit();
public:
	Building();

public:
	string m_LivingRoom;
private:
	string m_BedRoom;
};
//类外实现成员函数
Building::Building() {
	m_LivingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodFriend::GoodFriend() {
	b = new Building;
}
void GoodFriend::visit() {
	cout << "1好朋友正在访问" << b->m_LivingRoom << endl;
	cout << "1好朋友正在访问" << b->m_BedRoom << endl;
}
void GoodFriend::visit2() {
	cout << "2好朋友正在访问" << b->m_LivingRoom << endl;
	/*cout << "好朋友正在访问" << b->m_BedRoom << endl;*/
}
void test01() {
	GoodFriend g;
	g.visit();
	g.visit2();

}
int main() {

	test01();
	system("pause");
	return 0;
}

注意事项:

  • 将 GoodFriend类声明在Building类前面,这是因为编译器从上到下编译代码,visit() 函数体中用到了 Building类的成员 m_LivingRoom和m_BedRoom,如果提前不知道 Building的具体声明内容,就不能确定 Building类是否拥有该成员(类的声明中指明了类有哪些成员)。
  • 一个函数可以被多个类声明为友元函数,这样就可以访问多个类中的 private 成员。

5、运算符重载

运算符重载(Operator Overloading):同一个运算符可以有不同的功能。

通过运算符重载,扩大了C++已有运算符的功能,使之能用于对象。

加号运算符重载

把两个对象的成员分别相加,返回最终的对象

#include<iostream>
using namespace std;

//加号运算符重载

//1.成员函数做加号运算符重载
class P {
public:
	int m_A;
	int m_B;
	P() {};
	P(int a, int b);
    //重载运算符有一个返回类型和一个参数列表
	//P operator+(P& p) 对象作为参数进行传递
	//{
	//	P temp;
	//	temp.m_A = this->m_A + p.m_A;  对象的属性使用 this 运算符进行访问
	//	temp.m_B = this->m_B + p.m_B;
	//	return temp;
	//}
};

P::P(int a, int b) 
{
	this->m_A = a;
	this->m_B = b;
}

//2.全局函数做加号运算符重载
P operator+(P& p1, P& p2) 
{
	P temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
void test01() 
{
	P p1(4, 8);
	P p2(6, 2);
	P p3 = p1 + p2; //可以直接用operator+,也可以用+号
	cout << p3.m_A << endl;
	cout << p3.m_B << endl;
}

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

左移运算符重载

cout 是 ostream 类的对象,cin 是 istream 类的对象

#include<iostream>
using namespace std;

//左移运算符重载
class P {
    //了能够直接访问private 成员变量,同样需要将该函数声明为友元函数:
	friend ostream& operator<< (ostream& cout, P& p);
private:
	int m_A;
	int m_B;
public:
	P(int a, int b) {
		m_A = a;
		m_B = b;
	}
};

//只能用全局函数来重载左移运算符
//由于cout全局只能有一个,所以要采用引用的方式去拿到
ostream& operator<< (ostream& cout, P& p) {

	cout << "p.m_A的值为:" << p.m_A << "  p.m_B的值为:" << p.m_B;
	return cout;//返回了对象的引用,所以重载后的运算符可以实现连续输出

}
int main() {
	P p(10, 20);
	cout << p << " 你好呀" << endl;
	system("pause");
	return 0;
}

重载++和–运算符

自增++和自减--都是一元运算符,它的前置形式和后置形式都可以被重载。

#include<iostream>
using namespace std;

class MyInterger {
	friend ostream& operator<<(ostream& cout, MyInterger myInt);
public:
	MyInterger() {
		m_Int = 0;
	}

	//重载前置++运算符
	MyInterger& operator++() {
		m_Int++;
		return *this;
	}
	//重载后置++运算符
	//int表示占位参数,可以用来区分前置和后置运算符
	MyInterger operator++(int) {
		MyInterger temp = *this;//先记录当前结果,再进行++操作
		m_Int++;
		return temp;
	}
	//重载前置--运算符
	MyInterger& operator--() {
		m_Int--;
		return *this;
	}
	//重载后置--运算符
	//int表示占位参数,可以用来区分前置和后置运算符
	MyInterger operator--(int) {
		MyInterger temp = *this;//先记录当前结果,再进行--操作
		m_Int--;
		return temp;
	}
private:
	int m_Int;
};

//重载左移运算符
ostream& operator<<(ostream& cout, MyInterger myInt) {
	cout << myInt.m_Int;
	return cout;
}
void test01() {
	MyInterger myInt;
	cout << ++myInt << endl;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

void test02() {
	MyInterger myInt;
	cout << myInt++ << endl;
	cout << myInt++ << endl;
	cout << myInt << endl;
}
void test03() {
	MyInterger myInt;
	cout << --myInt << endl;
	cout << --(--myInt) << endl;
	cout << myInt << endl;
}

void test04() {
	MyInterger myInt;
	cout << myInt-- << endl;
	cout << (myInt--)-- << endl;
	cout << myInt << endl;
}
int main() {
	cout << "前置++" << endl;
	test01();
	cout << "后置++" << endl;
	test02();
	cout << "前置--" << endl;
	test03();
	cout << "后置--" << endl;
	test04();
	system("pause");
	return 0;
}

赋值运算符重载

#include<iostream>
using namespace std;
//赋值运算符重载

class Person {
public:
	int* m_Age;

	Person(int age) {
		m_Age = new int(age);
	}

	~Person() {
		if (m_Age == NULL) {
			return;
		}
		else {
			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;
	}
};

void test01() {
	Person p(18);
	Person p3(20);
	Person p2(30);
	p3 = p = p2;
	cout << "p的年龄为:" << *p.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

关系运算符重载

#include <iostream>
using namespace std;

class Person {
public:
	Person(string name, int age) {
		m_Name = name;
		m_Age = age;
	}
	string m_Name;
	int m_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;
		}
	}
};

void test01() {
	Person p1("张三", 18);
	Person p2("张三", 34);

	if (p1 != p2) {
		cout << "p1和p2是不相等的" << endl;
	}
	else {
		cout << "p1和p2是相等的" << endl;
	}
}
int main() {
	test01();
	system("pause");
	return 0;
}

函数调用运算符重载

  1. 函数调用运算符()也可以进行重载
  2. 由于重载后的使用方式非常像函数的调用,因此称为仿函数
  3. 仿函数没有固定写法,非常灵活

6、继承

一个类从另一个类获取成员变量和成员函数的过程

基本语法

class 子类 : 继承方式 父类

子类也被称为 派生类,父类也被称为 基类

class Student: public People

三种继承方式

公共继承 public(常用)

  • 父类中的公共权限到子类中依旧是公共权限

  • 父类中的保护权限到子类中依旧是保护权限

  • 子类无法访问父类中的私有权限

保护继承 protected

  • 父类中的公共权限和保护权限在子类中变成了保护权限

  • 子类无法访问父类中的私有权限

私有继承 private(默认)

  • 父类中的公共权限和保护权限到子类中变成了私有权限

  • 子类无法访问父类中的私有权限

不同继承方式对不同属性的成员的影响结果:

在这里插入图片描述

protected 成员和 private 成员类似,都不能通过对象访问。但是当存在继承关系时,protected 和 private 就不一样了;父类中的 protected 成员可以在子类中使用,而父类中的 private 成员不能在子类中使用。

改变访问权限

使用 using 关键字可以改变父类成员在子类中的访问权限,例如将 public 改为 private、将 protected 改为 public。

注意:using 只能改变父类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为父类中 private 成员在子类中是不可见的,根本不能使用,所以父类中的 private 成员在子类中无论如何都不能访问。

#include<iostream>
using namespace std;

//父类Teacher
class Teacher {
public:
    void show();
protected:
    char m_name;
    int m_age;
};
void Teacher::show() {
    cout << m_name << "的年龄是" << m_age << endl;
}

//子类Student
class Student : public Teacher {
public:
    void learning();
public:
    using Teacher::m_name;  //将protected改为public
    using Teacher::show;  //将public改为protected
    float m_score;
private:
    using Teacher::m_age;  //将protected改为private
};
void Student::learning() {
    cout << "我是" << m_name << ",今年" << m_age << "岁,这次考了" << m_score << "分!" << endl;
}

int main() {
    Student stu;
    stu.m_name = '小明';
    stu.m_age = 16;//编译错误
    stu.m_score = 99.5f;
    stu.show();  
    stu.learning();
    return 0;
}

继承时同名成员处理方式

遮蔽问题:如果子类中的成员(包括成员变量和成员函数)和父类中的成员重名,那么就会遮蔽从父类继承过来的成员,不管它们的参数是否一样。在子类中使用该成员(包括在定义子类时使用,也包括通过子类对象访问该成员)时,实际上使用的是子类新增的成员,而不是从父类继承来的;而且父类成员函数和子类成员函数不会构成重载。

访问静态和非静态同名成员规则:

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

  • 访问父类同名成员时,需要加上作用域

# include<iostream>
using namespace std;

//继承同名成员处理方式
class Base {
public:
	Base() {
		m_A = 100;
	}
	int m_A;
	void say() {
		cout << "我是父类的say函数" << endl;
	}
};

class Son :public Base {
public:
	Son() {
		m_A = 200;
	}
	int m_A;
	void say() {
		cout << "我是子类的say函数" << endl;
	}
};

void test01() {
	Son s;
	cout << "子类的m_A变量= " << s.m_A << endl;
	cout << "父类下面的m_A变量= " << s.Base::m_A << endl;
}

void test02() {
	Son s;
	s.say();
	s.Base::say();
}
int main() {
	cout << "同名的成员属性处理方式" << endl;
	test01();
	cout << "同名的成员函数处理方式" << endl;
	test02();
	system("pause");
	return 0;
}

父类和子类的构造函数

类的构造函数不能被继承。因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。但是子类的构造对象可以调用父类的构造函数。

构造函数调用顺序:先调用父类,再调用子类,只能调用直接父类的构造函数,不能调用间接父类的

析构函数调用顺序:先调用子类,再调用父类

多继承

C++支持多继承(Multiple Inheritance),即一个子类可以有两个或多个父类

语法:

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

多继承可能导致父类中同名成员的出现,需要加作用域加以区分

c++实际开发中,不建议用多继承

菱形继承

两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承或者钻石继承:

菱形继承

如上:类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A–>B–>D 这条路径,另一份来自 A–>C–>D 这条路径。

在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B–>D 这条路径,还是来自 A–>C–>D 这条路径。

//间接基类A
class A {
protected:
    int m_a;
};

//直接基类B
class B : public A {
protected:
    int m_b;
};

//直接基类C
class C : public A {
protected:
    int m_c;
};

//派生类D
class D : public B, public C {
public:
    void seta(int a) { m_a = a; }  //命名冲突,为了消除歧义,可以在 m_a 的前面指明它具体来自哪个类
    void setb(int b) { m_b = b; }  //正确
    void setc(int c) { m_c = c; }  //正确
    void setd(int d) { m_d = d; }  //正确
private:
    int m_d;
};

int main() {
    D d;
    return 0;
}

虚继承

虚继承解决了多继承时的命名冲突和冗余数据问题,使得在派生类中只保留一份间接基类的成员:

菱形继承和虚继承

在继承方式前面加上 virtual 关键字就是虚继承:

//间接基类A
class A {
protected:
    int m_a;
};

//直接基类B
class B : virtual public A {  //虚继承
protected:
    int m_b;
};

//直接基类C
class C : virtual public A {  //虚继承
protected:
    int m_c;
};

//派生类D
class D : public B, public C {
public:
    void seta(int a) { m_a = a; }  //正确
    void setb(int b) { m_b = b; }  //正确
    void setc(int c) { m_c = c; }  //正确
    void setd(int d) { m_d = d; }  //正确
private:
    int m_d;
};

int main() {
    D d;
    return 0;
}

这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

7、多态

“多态(polymorphism)”指的是同一名字的事物可以完成不同的功能。

多态可以分为编译时的多态和运行时的多态。

编译时的多态(静态多态):主要是指函数的重载(包括运算符的重载)、对重载函数的调用,在编译时就能根据实参确定函数地址,然后确定调用哪个函数。

运行时的多态(动态多态):和继承、虚函数等概念有关,运行阶段确定函数地址。

构成动态多态的满足条件:

  • 有继承关系
  • 子类重写父类的虚函数
  • 存在父类的指针,通过该指针调用虚函数。

动态多态使用:父类的指针或引用指向子类对象

虚函数

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

虚函数是根据指针的指向来调用的,指针指向哪个类的对象就调用哪个类的虚函数。

纯虚函数

将虚函数声明为纯虚函数,语法格式为:

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

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。

抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。

只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。

通常是基类作为抽象类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化

虚析构和纯虚析构

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

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

虚析构和纯虚析构异同:

同:

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

异:

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

语法:

//虚析构
virtual ~类名(){}
//纯虚析构
virtual ~类名(){} = 0;

5、文件操作

操作文件的三大类

C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:

  • ifstream:专用于从文件中读取数据;
  • ofstream:专用于向文件中写入数据;
  • fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。

这 3 个文件流类都位于 <fstream> 头文件中,因此在使用它们之前,程序中应先引入此头文件。

这 3 个文件流类的继承关系:

img

fstream类常用成员方法:

在这里插入图片描述

文件类型

文本文件:文件以文本的ASCII码形式存储在计算机

二进制文件:文件以文本的二进制形式存储在计算机中

打开文件

在对文件进行读写操作之前,先要打开文件。可以通过以下两种方式打开文件:

  • 调用流对象的 open 成员函数打开文件。
  • 定义文件流对象时,通过构造函数打开文件。

使用open函数打开文件:

void open(const char* szFileName, int mode)
  • 第一个参数是指向文件名的指针,

  • 第二个参数是文件的打开模式标记,文件的打开模式标记代表了文件的使用方式,这些标记可以单独使用,也可以利用|操作符组合使用。

下标列出各种模式标记单独使用时的作用,以及常见的两种模式标记组合的作用:

模式标记适用对象作用
ios::inifstream fstream打开文件用于读取数据。如果文件不存在,则打开出错。
ios::outofstream fstream打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。
ios::appofstream fstream打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。
ios::ateifstream打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。
ios:: truncofstream打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。
ios::binaryifstream ofstream fstream以二进制方式打开文件。若不指定此模式,则以文本模式打开。
ios::in | ios::outfstream打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::outofstream打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out | ios::truncfstream打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。

1、文本文件

写文件

//包含头文件
#include<fstream>

using namespace std;

#include<iostream>

void test()
{
    //创建流对象
	ofstream ofs;
//打开文件
	ofs.open("test.txt", ios::out);
//写入文件内容
	ofs << "姓名:张三" << endl;
	ofs << "年龄:118" << endl;
	ofs << "性别:男" << endl;
//关闭流
	ofs.close();
}

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

读文件

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

void test()
{
	ifstream ifs;

	ifs.open("test.txt", ios::in);
	if (!ifs)
	{
		cout << "文件打开出错" << endl;
		return;
	}
	//4.读数据
	//第一种
	char buf[1024] = {0};
	while (ifs >> buf) {
		cout << buf << endl;
	}
	//第二种
	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){
		cout << c;
	}
	
	ifs.close();
}

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

2、二进制文件

写文件

将内存中 buffer 指向的 count 个字节的内容写入文件

ostream & write(char* buffer, int count);
  • buffer 用于指定要写入文件的二进制数据的起始位置;

  • count 用于指定写入字节的个数。

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

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

void test()
{
	ofstream ofs;

    //以二进制文本模式打开文件
	ofs.open("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;
}

读文件

从文件中读取 count 个字节的数据

istream & read(char* buffer, int count);
  • buffer 用于指定读取字节的起始位置,
  • count 指定读取字节的个数
#include<fstream>
using namespace std;
#include<iostream>

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

void test()
{
	ifstream ifs;

	ifs.open("person.txt", ios::out | ios::binary);

	if (!ifs)
	{
		cout << "文件读取失败" <<endl ;
		return;
	}
	Person p;
	ifs.read((char*)&p, sizeof(p));
	cout << "p的姓名" << p.m_name << "p的年龄:" << p.m_age << endl;
	ifs.close();
}

int main()
{
	test();
	system("pause");
	return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 20:38:18  更:2022-09-24 20:41:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 11:43:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码