C++核心技术个人笔记

Posted ZSYL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++核心技术个人笔记相关的知识,希望对你有一定的参考价值。

【C++核心技术】个人笔记

四大内存分区&引用

  • 全局区
// 全局变量,main函数外面定义的

// 静态变量

static int a = 10;

// 常量:字符串常量
"string"

// const修饰的全局变量(也放常量区)
const int a, b, c = 10;

  • 栈区
    由编译器自动分配和释放(形参也会放在栈区)
int* func()

	int a = 10;  // 局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
	return &a;  // 返回局部变量的地址


int main() 
	// 接受func函数的返回值
	int *p = func();
	cout << *p << endl;  // 解引用 第一次可以打印,编译器做了保留
	cout << *p << endl;  // 解引用 第二次不再保留
	
  • 堆区
    程序员分配释放,程序结束时,由操作系统回收
    使用new在堆区开辟内存
int* func() 
	int * p = new int(10);  // 使用new开辟到堆区
	return p;  // 指针是局部变量,保存在堆区

  • new 关键字
    如何释放内存:delete 变量;
    cout << *p << endl;:内存已释放,再次访问会报错
int * arr = new int[10]; // 创建数组,也是一个指针
arr[i]:也可以操作数组 

释放堆区数组:delete[] arr;

  • 引用
    数据类型 & 别名 = 原名:操作同一块内存
1.引用必须初始化
2.引用初始化之后不能改变
3.引用=c,赋值操作,而非更改引用
  • 引用传递
// 地址传递,形参会修饰实参
void swap(int *a, int *b) 
	int temp = *a;  // 解引用
	*a = *b;
	*b = temp;

// 引用(起别名)传递,形参会修饰实参
// 相当于起别名,修饰同一块内存
void swap(int &a, int &b) 
	int temp = a;
	a = b;
	b = temp;

  • 引用做函数的返回值
// 1. 不要返回局部变量的返回值
int & test() 
	int a = 10; // 局部变量存放在栈区
	return a;

int & test2() 
	static int a = 10;  // 静态变量,存放在全局区,程序结束后会销毁
	return a;


int main() 
	 int &res = test2();  // 正常访问
	 // 如果函数做左值,那么必须返回引用
	 test02() = 1000;  // 赋值操作,函数返回的就是static a的地址
	 // res = 1000;

  • 引用的本质是指针常量,因此初始化后不能修改指向的地址。
int a = 10;
int& ref = a;  // int * const ref = &a;
// 输出引用时,会默认解引用
ref = 20; // 内部发现ref是引用,会自动转化为:*ref = 20;
  • 常量引用:用来修饰形参,防止误操作,改变实参
int a = 10;
// 加上const之后,编译器将代码修改为 int temp=10, const int & ref = temp;
const int & ref = 10;  // 引用必须为一块的合法的内存空间
// ref = 20; 加上const之后为只读,不可以修改。
void show(const int & a) 
	// a = 100; 报错
	// 不允许修改该引用

  • 打印地址:(int)&a

函数提高

默认参数

  1. 如果一个位置有了默认参数,那么这个位置往后都必须有默认值
  2. 如果函数声明有默认参数,函数实现就不能有默认参数(声明和实现只能有一个有默认参数)
// 函数声明(没有实现)让编译器知道有这个函数,后面就可以用了
int func(int a=10, int b=10);
// 实现(二义性有误,不知道声明和实现要哪个)
int func(int a=20, int b=20) 
	return a+b;

占位参数

形参列表可以有占位参数,用来占位,占位参数也有默认参数。

  • 占位参数,传参数时必须填补
void func(int a, int = 10) // 无意义

函数重载

函数名可以相同,提高复用性

满足条件:

  • 同一作用域下
  • 函数名称相同
  • 函数参数类型不同/个数不同/顺序不同
void func() 
	cout << "func调用" << endl;

void func(int a) 
	cout << "func的重载" << endl;

  • 函数的返回值不能作为重载的条件,因为编译器不知道调用哪个(二义性)
  1. 引用作为重载的条件
void func(int &a) 


// 类型不同
void func(const int &a)  // const int &a=10;  合法操作	


int main() 
	int a = 10;
	func(a);  // 调用第一个,变量可读可写 
	func(10);  // 报错,第一个函数中,引用必须是合法的内存空间(栈/堆)10是常量区中,因此执行第二个重载函数
	// const int &a = 10;是合法的,编译器创建一个临时的变量10

  • 引用必须是合法的内存空间(栈/堆)10是常量区中
  1. 函数重载遇到默认参数,出现二义性,尽量避免。

面向对象

封装

class Circle 
	// 访问权限
public:
	int r;
	string name;
	// 行为
	double calculate() 
		return 2 * PI * r;
	
	
	void setC(int c) 
		r = c;
	
;
int main() 
	// 创建对象
	Circle c1;
	c1.r = 1;
	c1.calculate();

  • 使用string,需要包含头文件#include <string>
  • 类中属性和行为统称为成员
  • 属性:成员变量
  • 行为:成员方法

权限

  1. public:全都可以访问
  2. protected:类内,子类可以访问
  3. private:类内可以访问
class Circle 
	// 访问权限
public:
	int r;
private:
	string name;
	// 行为
protected:
	double calculate() 
		return 2 * PI * r;
	
	
	void setC(int c) 
		r = c;
	
;

struct和class区别:

  • struct:默认public
  • class:默认private

属性设置为私有,则设置公有接口来实现对其操作

  • 头文件 name.h:里面函数只有声明
  • cpp代码中引入头文件报错,需要加入作用域:
void Point::setX() 

构造函数

析构函数,对象销毁前会调用一次。

~Person() 

浅拷贝

问题:堆区重复释放

//拷贝构造函数  
Person(const Person& p) 
	cout << "拷贝构造函数!" << endl;
	//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
	m_age = p.m_age;
	m_height = new int(*p.m_height);
	

初始化

//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) 
// 初始化
Person (string name, string pName): m_Name(name), m_Phone(pName)


string name;
Phone m_Phone;

静态函数

  1. 对象调用
  2. 类名调用 Person::func(); // 作用域

类内声明静态成员变量:

static int m_A;  // 静态成员变量

// 类外初始化
int Person::m_A = 0;

所有对象共享一份数据。

静态成员变量两种访问方式:

  • 对象
  • 类名

静态函数也有权限,private: 类外不能访问。

静态成员函数特点

  • 程序共享一个函数
  • 静态成员函数只能访问静态成员变量

静态成员函数两种访问方式:

  • 通过对象
  • 类名
    Person::func();

类和对象

  • 空对象占用内存空间为:1
  • C++编译器也会给每一个对象分配一个字节空间,是为了区分空对象占内存的位置
  • 每一个对象也应该有一个独一无二的内存地址
  • 非静态成员变量占对象空间
  • 静态成员变量不占对象空间
  • 函数也不占对象空间,所有函数共享一个函数实例
  • 如果用到this指针,需要加以判断保证代码的健壮性
  • 空指针访问成员函数,但是如果成员函数中用到了this指针,就不可以了

const

  • this指针的本质是一个指针常量,指针的指向不可修改,但是this指针指向的对象的数据是可以修改的
  • 如果想让指针指向的值也不可以修改,需要声明常函数
  • const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
  • const修饰对象 常对象
  • 常对象不能修改成员变量的值,但是可以访问
  • 常对象访问成员函数,常对象不能调用const的函数

友元

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

运算符重载

bool operator==(Person & p)
	
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		
			return true;
		
		else
		
			return false;
		
	

继承

class Java : public BasePage

	.....

class A : public B;

A 类称为子类 或 派生类

B 类称为父类 或 基类
  • 继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。

  • 当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数

  • 如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域 s.Base::m_A
    总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

多继承

多继承容易产生成员同名的情况

  • 通过使用类名作用域可以区分调用哪一个基类的成员

菱形继承

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal ;
class Tuo   : virtual public Animal ;
class SheepTuo : public Sheep, public Tuo ;

总结:

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  • 利用虚继承可以解决菱形继承问题

多态

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
  • 我们希望传入什么对象,那么就调用什么对象的函数
  • 如果函数地址在编译阶段就能确定,那么静态联编
  • 如果函数地址在运行阶段才能确定,就是动态联编

总结:

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

void DoSpeak(Animal & animal)

	animal.speak();

抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

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

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
  • base = new Base; // 错误,抽象类无法实例化对象

纯虚构

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

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

虚析构和纯虚析构共性:

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

虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名()

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名()

文件操作

C++中对文件操作需要包含头文件 < fstream >

写文件

   写文件步骤如下:

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二进制方式
  • 二进制方式写文件主要利用流对象调用成员函数write
ofs.write((const char *)&p, sizeof(p));
  • 文件输入流对象 可以通过read函数,以二进制方式读数据
Person p;
	ifs.read((char *)&p, sizeof(p));

去年大三时候的东西,未完待续!

以上是关于C++核心技术个人笔记的主要内容,如果未能解决你的问题,请参考以下文章

黑马程序员 C++教程从0到1入门编程笔记3C++核心编程(内存分区模型引用函数提高)

虚幻C++入门个人笔记——UMG网络

核心技术汇集网址开辟

《图解HTTP》-读数笔记

c++个人笔记总结

「春招系列」MySQL面试核心25问(附答案)