Cpp_learning_note_2
[toc]
reference:《黑马》C++系列教程 基础 核心 提高
本篇主要参考核心篇进行记录
内存分区
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存,释放利用操作符 delete,note释放数组 delete 后加 []
引用
不要返回局部变量引用,可以返回静态变量引用 static
引用的本质,在C++内部实现是一个指针常量
int a = 10;
int& ref = a;
//自动转化为int* const ref = &a; 指针常量是指针指向不可改
- 在函数形参列表中,可以加const修饰形参,防止形参改变实参
int& ref = 10; //是错误的,引用本身需要一个合法的内存空间,不能直接赋值
const int& ref = 10;
//加入const,编译器会优化代码,int temp = 10; const int& ref = temp;
函数重载
//1.引用作为重载条件
void func(int& a)
void func(const int& a)
int a = 10;
func(a);
func(10);//调用不同的函数
//2.函数重载遇到函数默认参数
func2(int a, int b = 10)
func2(int a)
//这样的定义在调用的时候会报错
func2(10);//产生歧义,不能做出调用函数的判断
类和对象
OOP - 封装、继承、多态
一、about 类
- 语法:class 类名{ 访问权限: 属性 / 行为};
- 类在设计时,可以把属性和行为放在不同权限下,加以控制
访问权限有三种:
– public 公共权限: 类内可以访问,类外可以访问(只允许本类的成员函数访问)
– protected 保护权限 :类内可以访问,类外不可以访问 (只允许子类及本类的成员函数访问)
– private 私有权限: 类内可以访问,类外不可以访问(可以被任意实体访问)
- 在C++中 struct 和 class 唯一的区别就在于默认的访问权限不同,struct默认权限为公共,class默认权限为私有
二、concerning 对象
- 对象的初始化和清理 – 构造函数和析构函数
如果编程者不提供,编译器会自动提供空实现的构造和析构函数(这时候编译器自动添加的拷贝构造对属性的拷贝是浅拷贝
–构造函数语法: 类名(){}
- 构造函数没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以参数,可能发生函数重载
- 程序在调用对象的时候会自动调用构造,且只调用一次
分类 按参数:有参构造、无参构造 按类型:普通构造、拷贝构造
调用方式 括号法、显式法(类似java)、隐式转换法
*tips:
调用无参构造函数不能加括号,加了编译器认为这是一个函数声名;
不能利用拷贝构造函数初始化匿名对象,编译器认为是对象声明;
拷贝构造函数的调用时机:
- 使用一个已创建好的对象初始化新的对象
- 值传递的方式给函数参数传值
- 以值的方式返回局部对象
– 析构函数语法:~类名(){}
- 析构函数没有返回值也不写void
- 函数名称与类名相同,前面加上符号~
- 析构函数不可以有参数,不能发生重载
- 程序在对象销毁前会自动调用析构,且只调用一次
(一)深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间进行拷贝操作
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的在delete时出现的问题
new -> 在堆空间创建一块内存并返回内存地址给指针
int* p = new int(10);//这里定义变量类型为指针
(二)初始化列表
语法:构造函数(): 属性1(值1), 属性2(值2), …{}
只有构造函数可以使用初始化列表语法,引用数据成员与const数据成员必须使用这种语法,因为它们在创建时就需要初始化
class Person{
public:
Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) {}
private:
int m_A:
int m_B;
int m_C;
}
(三)类对象作为类成员
当类中成员时其他类对象时,我们称该成员为 对象成员
构造的顺序是:先调用对象成员的构造,再调用本类构造。析构的顺序与构造相反
(四)静态成员
在成员变量和成员函数前加上关键字static,称为静态成员
静态成员变量(2种访问方式:通过对象、通过类名)
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
class Person{ public: static int m_A; private: static int m_B; } int Person::m_A = 10; int Person::m_B = 10;//类外初始化 cout << Person::m_A << endl;//通过类名访问 cout << Person::m_B << endl;//会报错,没有访问权限静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
三、C++对象模型和this指针
在C++中,类内的成员变量和成员函数 分开储存,只有非静态成员变量才属于类的对象上。非静态成员变量占用对象空间,而静态成员变量和成员函数(静态/非静态)都不占用对象空间,所有非静态成员函数共享一个函数实例。
this指针 指向被调用成员函数所属的对象
- this指针是隐含在每一个非静态成员函数内的一种指针
- 不需要定义可以直接使用
- 用途:当形参和成员变量同名时,用this指针区分;在类的非静态成员函数中返回对象本身,可使用return *this
- 空指针也可以调用成员函数,但是当成员函数用到了this指针则不可以,这个时候需要对指针进行非空判断,保证代码的健壮性
Person* p = NULL; if(this == NULL) return;//非空判断- 常函数:成员函数后加const,在常函数内不可以修改成员属性,但是当成员属性声明时添加mutable关键字,在常函数内仍可以进行修改
void func() const {...} public: int m_A; mutable int m_B;- 常对象:声明对象前加const,常对象只能调用常函数,不能修改成员变量的值,但是可以访问
const Person person;
四、友元 -> 让一个函数或者类访问另一个类中的私有成员
关键字 friend
三种实现
- 全局函数左右元
- 类做友元
- 成员函数做友元
五、运算符重载 -> 对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
函数名由关键字 operator 和其后要进行重载的运算符符号构成的
通俗理解就是将数的相加、输出、比较判断等运算重载为直接对对象进行操作
六、继承
class A: public B { }; 其中A类称为子类 或 派生类,B类成为父类 或 基类
派生类中的成员包含从基类继承的和自己增加的成员
- 继承方式 语法: class 子类: 继承方式 父类
- 公共继承 public
- 保护继承 protected
- 私有继承 private
| public | protected | private | |
|---|---|---|---|
| 公有继承 | public | protected | 不可见 |
| 保护继承 | protected | protected | 不可见 |
| 私有继承 | private | private | 不可见 |
tips:
父类中私有成员也被子类继承下去了,只是由编译器隐藏后不能访问
继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
- 继承同名成员的处理方式
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要添加作用域
class Base {
public:
Base(){m_A = 100;}
void func(){}
int m_A;
}
class Son: public Base{
public:
Son(){m_A = 200;}
void func(){}
int m_A;
}
void main(){
Son s;
s.func();//直接访问子类同名成员
s.Base::func();//添加作用域访问父类同名成员
}//这里是子类以对象的方式进行访问
*tips:对于同名静态成员处理方式与上面相同,不过有通过对象和通过类名两种访问方式
- 多继承语法:class 子类: 继承方式 父类1, 继承方式 父类2 …
- 菱形继承(草泥马的例子好形象2333)
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
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 test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
*tips:采用虚继承解决菱形继承的问题,虚继承的目的是让某个类做出声明,承诺愿意共享它的基类
七、多态
分类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
区别:静态–函数地址早绑定 - 编译阶段确定函数地址
动态–函数地址晚绑定 - 运行阶段确定函数地址
class Animal{
public:
virtual void speak(){}//函数前加上virtual关键字,变成虚函数,编译器在编译的时候就不能确定函数调用
};
class Cat:public Animal{//继承Animal类
public:
virtual void speak(){}//Q:这里为virtual为什么可以省略不写
};
class Dog:public Animal{
public:
virtual void speak(){}
};
void DoSpeak(Animal & animal){
animal.speak();
}
int main(){
Cat cat;
DoSpeak(cat);//传入什么对象,就调用什么对象的寒素
Dog dog;
DoSpeak(dog);
return 0;
}
- 多态满足条件:1. 有继承关系 2. 子类重写父类中的虚函数
- 多态使用:父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
在多态中,通常父类中虚函数饿实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
- 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了虚函数(虚构造函数或虚析构函数),这个类也成为抽象类(类中只要有一个纯虚函数就成为抽象类)
抽象类特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
Q:多态使用时,子类中开辟属性到堆区(new),父类指针在释放时无法调用子类的析构代码
A:将父类中的析构函数改为虚析构或纯虚析构
- 虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象的问题
- 都需要有具体的函数实现
- 虚析构和纯虚析构区别:
- 纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名 () {}
纯虚析构语法:类内 -> virtual ~类名() = 0; 类外 -> 类名::~类名() {}
文件操作
C++中对文件操作许哟啊包含头文件<fstream>
文件类型:
- 文本文件:以ASCII码形式储存在计算机中
- 二进制文件:以二进制形式存储在计算机中
操作文件的三大类:
- ofstream:写操作
- ifstream:读操作
- fstream:读写操作
文本文件
- 写文件
- 包含头文件
#include<fstream> - 创建流对象
ofstream ofs; - 打开文件
ofs.open("file_path", way); - 写数据
ofs<<"writing data"; - 关闭文件
ofs.close();
- 包含头文件
- 文件打开方式
| 打开方式 | 解释 |
|---|---|
| ios::in | 为读文件而打开文件 |
| ios::out | 为写文件而打开文件 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 追加方式写文件 |
| ios::trunc | 如果文件存在先删除,再创建 |
| ios::binary | 二进制方式 |
文件打开方式可以配合使用,利用|操作符
e.g. 用二进制方式写文件ios::binary|ios::out
读文件
包含头文件
#include<fstream>创建流对象
ifstream ifs;打开文件
ifs.open("file_path", way);,利用is_open函数可以判断文件是否打开成功读数据 四种方式
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();
二进制文件
打开方式指定为ios::binary
- 写文件 利用流对象调用成员函数wirte
函数原型 ofstream& write(const char * buffer,int len);
- 写文件 利用流对象调用成员函数read
函数原型 ifstream& read(char *buffer,int len);