【C++学习笔记】C++核心编程

C++核心编程

1 内存模型

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

  1. 代码区:存放函数体的二进制代码,由操作系统进行管理的
  2. 全局区:存放全局变量和静态变量以及常量
  3. 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  4. 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

1.1、程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:
全局变量和静态变量存放在此,
全局区还包含了常量区,字符串常量和其他常量也存放在此
该区域的教据在程序结束后由操作系统释放

1.2、程序运行后

栈区:
由编译器自动分配释放,存放函数的参数值,局部变是等
注意事项:不要返回同部变量的地址,栈区开辟的数据由编译器自动释放

堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存

2 引用

2.1、引用基本操作

int a=10;
int &b=a;

注意

  1. 引用必须初始化
    int &b; // 错误的
  2. 引用一旦初始化,就不可以更改

2.2、引用做函数参数

引用传递:使用形参修改实参,简化了指针修改实参
void test(int &a, int &b)

2.3、引用做函数返回值

int& test(int a, int b)

  1. 不要返回局部变量的引用
int& test(int a, int b){
		int a=10;
		return a;
	} // 错误的,函数结束后内存被释放
  1. 函数的调用可以作为左值
int& test(int a, int b){
	static int a=10;
	return a;
} // 静态变量,存放在全局区,在系统结束后才会被释放

函数作左值: test()=1000;

2.4、引用的本质

引用本事是一个指针常量(指针的指向不可修改,指针的值可以修改)int * const

2.5、常量引用

主要用来修饰形参,防止误操作
只能读不能写

void show(const int &a){
	cout<< a <<endl;
}

3 函数提高

3.1、函数默认参数

int func(int a,int b,int c=10){}

注意

  1. 某个位置已经有了默认参数,则之后的参数都要有默认参数
  2. 如果函数声明有默认参数,则函数实现就不能有默认参数

3.2、函数占位参数

int func(int a,int ){}

注意

  1. 使用时占位参数必须有实参
  2. 占位参数可以有默认参数int func(int a,int =10 )
  3. 占位参数如何使用?

3.3、函数重载

函数名相同,参数类型、个数、顺序不同
函数返回值不能作为重载条件
注意

  1. 引用作为重载条件
int func(int &a){} 
int func(const int &a){}

int c=10
func(c); //调用第一个 int &a=c;
func(c); //调用第二个 const &a=10;
  1. 重载碰到函数默认参数
int func(int a){} 
int func(int a, int b=10){} 

func(a) // 有二义性,错误

4 类和对象

封装、继承、多态

4.1、封装

4.1.1 封装的意义

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

4.1.2 访问权限
  1. public 公共权限 成员类内可以访问,类外也可以访问
  2. protected 保护权限 成员类内可以访问,类外不可以访问 子类可以访问父类的保护内容
  3. private 私有权限 成员类内可以访问,类外不可以访问 子类不可以访问父类的私有内容
    struct和class的区别
    默认权限不同,struct:公有,class:私有
4.1.3 成员属性设置为私有
  1. 可以控制读写权限
  2. 对于写操作,可以检测数据有效性

4.2、对象初始化和清理

4.2.1 构造函数和析构函数

构造函数:创建对象时赋值
析构函数:销毁前自动调用
构造函数: 类名(){}

  1. 可以有参数,可以重载
  2. 没有返回值,只会调用一次
    析构函数: ~类名(){}
  3. 没有返回值,只会调用一次
  4. 不能有参数,不可重载
4.2.2 构造函数的分类

两种分类方式:
按参数:有参构造、无参构造(默认构造)
按类型:普通构造、拷贝构造
三种调用方式:
括号法
显示法
隐式转换法

  1. 无参构造:Person(){}
  2. 有参构造:Person( int a ){}
  3. 普通构造:Person(){}
  4. 拷贝构造:Person( const Person &p ){}
  5. 括号法:Person p1/ Person p2(10)/ Person p3(p2)调用默认构造方法不需要加(),因为编译器会认为是一个函数声明
  6. 显示法:Person p1/ Person p2=Person(10)/ Person p3=Person(p2)不要用拷贝构造初始化一个匿名对象,编译器会认为你在创建一个重名对象
  7. 隐式转换法:
    Person p1=10; //相当于 Person p1=Person(10)
    Person p1=p2; //相当于 Person p1=Person(p2)
4.2.3 拷贝构造函数的调用时机
class Person{
public:
	int m_Age;
public:
	Person(){
		
	}
	Person(int age){
		m_Age=age;
	}
	Person(const Person &p){
		m_Age=p.m_Age;
	}
}
  1. 使用一个已经创建完毕的对象来初始化一个新对象
	Person p1(10);
	Person p2(p1);
  1. 值传递的方式给函数参数传递
	void work( Person p){}
	Person p;
	work(p); //值传递会创建一个临时副本
  1. 以值方式返回局部对象
	void work(){
		Person p1;
		return p1;
	}
	Person p=work(); //局部对象返回时,会创建一个新的对象调用拷贝构造函数
4.2.4 构造函数的调用规则

默认情况,c++编译器会添加三个函数:默认构造函数、默认析构函数、默认拷贝构造函数

调用规则:如果用户提供了构造函数,c++就不用默认的了

4.2.5 深拷贝与浅拷贝

浅拷贝:简单赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

class Person{
public:
	int m_Age;
	int *m_Height;
public:
	Person(){
		
	}
	Person(int age, int height){
		m_Age=age;
		m_Heigh=new int(height);
	}
	// 自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person &p){
		m_Age=p.m_Age;
		m_Height=new int(*p.m_Height);
	}
	~Person(){
		
	}
}

在这里插入图片描述

4.2.6 初始化列表

用来初始化属性 构造函数(): 属性1(值1), 属性2(值2), 属性3(值3)

Person(int a, int b):m_Age(a),m_Height(b){}
4.2.7 类对象作为类成员

先构造A对象,再构造B对象
先析构B对象,再析构A对象

class A{}
class B{
	A a;
}
4.2.8 静态成员
  • 静态成员变量(有访问权限)
    所有对象共享同一份数据
    在编译阶段分配内存
    类内声明,类外初始化
  • 静态成员函数(有访问权限)
    所有对象共享同一个函数
    静态成员函数只能访问静态成员变量

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

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

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

class Person{ //空对象占1个字节内存,C++编译器给每个空对象一个字节用于区分
	int m A;//非静态成员变量 属于类的对象上
	static int m_B;//静态成员变量 不属于类对象上
	void func(){} //非静态成员函数 不属于类对象上
	static void func(){} //非静态成员函数 不属于类对象上
}
4.3.2 this指针

this指针指向被调用的成员函数所属的对象
this指针的本质是指针常量,指针的指向不能修改
this的用途:
区分同名的形参与成员变量
在非静态成员函数中如果要返回对象本身,return *this

4.3.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性

4.3.4 const修饰成员函数

常函数:

  • 成员函数后加const我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
class Person{
public:
	//常函数
	void showPerson() const{
	
	}
	int a;
	mutable b;
}
const Person p; //常对象

4.4 友元

目的是让一个函数或类可以访问到一个类的私有成员
友元的关键字为friend

友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
Bclass Building{
	friend void goodGay(Building *building); //全局函数做友元
	friend class GoodGay; //类做友元
	friend void GooGay::visit(); //成员函数做友元
public:
	Building(){
		m_SittingRoom = "客厅"
		m_BedRoom = "卧室";
	}

public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;// 卧室
}
void goodGay(Building *building){
	cout<< building->m_SittingRoom << building->m_BedRoom << endl;
}

4.5 运算符重载

4.5.1 加号运算符重载

实现两个自定义数据类型相加

//通过成员函数重载+号 (根据不同参数,operator+也可以重载)
Person operator+(Person &p){
	Person temp;
	temp.m_A = this->n_A + p.m_ A;
	temp.m_B = this-m B+P.m B;
	return temp;
}
Person p3=p1+p2;

对于内置的数据类型的表达式的的运算符是不可能改变的、不要滥用运算符重载

4.5.2 左移运算符重载

可以输出自定义的数据类型

class Person{
public:
	//利用成员函数重载 左移运算符p.operator<<(cout) 简化版本加<<cout
	//不会利用成员函数重载<<运算符,因为无法实现 cout在左侧
	void operator<<( cout ){}
	int m A;
	int m B;
}

//只能利用全局函数重载左移运算符
ostream& operator<<( ostream &cout, Person &p ){} //本质 operator<<(cout,p),简化 cout<<p
cout<<p<< "" <<endl;
4.5.3 递增运算符重载
class MyInt{
public:
	MyInt(){
		m_Num=0;
	}
	//重载前置++运算符(返回引用)
	MyInt& operator++(){
		m_Num++;
		return *this;
	}
	//重载后置++运算符(返回值,不返回引用)
	MyInt operator++(int){	//int代表占位参数,用于区分前置和后置递增
		MyInt temp=*this; //先记录
		m_Num++;	//再增加
		return temp;	//返回记录值
	}
private:
	int m_Num;
}
4.5.4 赋值运算符重载

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

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝
  • 赋值运算符 operator=,对属性进行值拷贝
class Person{
public:
	int *m_Height;
public:
	Person(int height){
		m_Heigh=new int(height);
	}
	Person& operator=(Person &p){
		//编译器提供浅拷贝
		//m_Height=p.m_Height;
		
		//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝if(m _Age != NULL)
		delete m Age;m Age = NULL:
		//深拷贝
		m Age = new int(*p.m Age)
		return *this;
	}
	~Person(){
		if(m_Heigh!=NULL){
			delete m_Height;
			m_Heigh=NULL;
		}
	}
}
4.5.5 关系运算符重载
class Person{
public:
	int m_Height;
public:
	Person(int height){
		m_Heigh=new int(height);
	}
	//关系运算符重载
	bool& operator>(Person &p){}
}
4.5.6 函数调用运算符重载

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

class Person{
public:
	int m_Height;
public:
	//关系运算符重载
	bool& operator()(int a){}
}
Person p;
p(10);

4.6 继承

4.6.1 继承基本语法

语法:class 子类(派生类) : 继承方式 父类(基类)

class BasePage{
public:
	void head(){};
	void foot(){};
}
class Java : public BasePage{
public:
	void content(){};
}
4.6.2 继承方式

继承方式一共有三种:

  • 公共继承:父类中:子类中 public:public;protected:protected;private:不可访问;
  • 保护继承:父类中:子类中 public:protected;protected:protected;private:不可访问;
  • 私有继承:父类中:子类中 public:private;protected:private;private:不可访问;
    在这里插入图片描述
4.6.3 继承中的对象模型

父类中所有的非静态成员都会被子类继承下去
私有成员只是被隐藏了,还是会继承下来

class Base{
public:
	int m A;
protected:
	int m B;	
private:
	int m_C; 	//只是被隐藏了,还是会继承下来
}
//利用开发人员命令提示工具查看对象模型
//cd 具体路径
//查看.cpp文件命名
cl /dl reportSingleClassLayout类名 文件名
4.6.4 继承中的构造和析构

子类继承父类后,构造与析构的顺序:

  • 构造函数:先父类,后子类
  • 析构函数:先子类,后父类
4.6.4 继承中的同名成员处理方式
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
    如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
    如果想访问父类中的同名成员函数,需要加作用域
class Base{
public:
	int m_A;
	void func(){}
}
class Son:public Base{
public:
	int m_A;
	void func(){};
}
Son s;
s.m_A; //子类中的m_A
s.Base::m_A; //父类中的m_A
s.func; //子类中的func
s.Base::func; //父类中的func
4.6.5 继承中的静态成员处理方式
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
4.6.6 多继承语法

语法:class 子类 : 继承方式 父类1, 继承方式 父类2
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承

4.6.6 菱形继承

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

  • 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
  • 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
    利用==虚继承(virtual)==可以解决菱形继承的问题
class Animal{
public:
	int m_Age;
}
//加virtual后变为虚继承
class Sheep : virtual public Animal{}
class Tuo: virtual public Animal{}
class YangTuo : public Sheep,public Tuo

在这里插入图片描述

4.7 多态

4.7.1 多态的基本概念

多态分为两类

  • 静态多态: 函数重载运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类虚函数实现运行时多态
    静态多态和动态多态区别:
  • 静态多态的函数 地址早绑定-编译阶段确定函数地址
  • 动态多态的函数 地址晚绑定-运行阶段确定函数地址
class Animal(
public:
	//虚函数
	virtual void speak(){
		cout<< "动物在说话"<<endl;
	}
)
class Cat : public Animal{
	virtual void speak(){
		cout<< "猫在说话"<<endl;
	}
}
//若地址早绑定,则执行Animal.speak()
//地址晚绑定,执行 Cat.speak()
void doSpeak(Animal &animal){	//Animal *animal = cat
	animal.speak();
}
Cat cat;
doSpeak(cat);

动态多态满足条件: 1、有继承关系;2、子类重写父类的虚函数;
动态多态的使用:父类的指针或引用 指向子类对象

4.7.2 多态的底层原理

在这里插入图片描述

4.7.3 纯虚函数和抽象类

多态中,父类的虚函数的实现毫无意义,主要调用子类重写的方法,因此可以将虚函数改为纯虚函数
语法:virtual 函数返回类型 函数名 (参数列表)=0
抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
4.7.4 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现

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

class Animal(
public:
	virtual void speak(){
		cout<< "动物在说话"<<endl;
	}
	//虚析构:可以解决父类指针释放子类对象不干净的问题
	virtual ~Animal(){}
	//纯虚析构
	virtual ~Animal()=0)
//纯虚析构需要有具体实现
Animal::~Animal(){
	cout<< "纯虚析构" <endl;
}

class Cat : public Animal{
public:
	Cat(string name){
		m_Name=new string(name);
	}
	virtual void speak(){
		cout<< "猫在说话"<<endl;
	}
	string *m_Name;
	~Cat(){
		if(m_Name!=NULL){
			delete m_Name;
			m_Name=NULL;
		}
	}
}
Animal *animal=new Cat("Tom");
delete animal; //父类的指针在析构的时候不会调用子类的析构函数。如果子类有存在堆区的数据,会造成内存泄漏

5 文件操作

包含头文件<fstream>

文件类型分为两种:

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

操作文件的三大类:

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

5.1 文本文件

5.1.1 写文件

写文件步骤如下:

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

文件打开方式:

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

注意:可以配合使用, ios::binary | ios:.in

5.1.2 读文件

读文件步骤如下:

1.包含头文件
	#include <fstream>
2.创建流对象
	ifstream ifs,
3.打开文件并判断文件是否打开成功
	ifs.open("文件路径",打开方式);
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 x;
	while((c=ifs.get()) != EOF){ //EOF: end of file
		cout<<c;
	}
5.关闭文件
	ifs.close();

5.2 二进制文件

打开方式要指定ios::binary

5.2.1 写文件

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

Person{
	char name[64];
	int age;
}
Person p = {"张三",18};
ofs.write((const char*)&p,sizeof(Person));

5.2.2 读文件

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

Person{
	char name[64];
	int age;
}
if(ifs.is_open()){
	Person p;
	ifs.read((char*)&p,sizeof(Person));
}

  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一往而情深

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值