文件操作, 文本文件,
(1)文件操作必须包含头文件 fstream
(2) 读文件可以可用ofstream, 或者fstream类
(3) 打开文件时候需要制定操作文件的路径, 以及打开方式
(4) 利用 <<可以向文件中写数据
(5) 操作完毕, 需要关闭文件.
文件操作的API函数
#include <fstream>
ifstream ifs
ifs.open 打开文件
ifs.is_open 判断
文件读取的操作
方式1,
char buf[1024]
while(ifs>>buf ) // 读到末尾, 返回false
方法2
ifs.getline(buf, sizeof(buf));
方法3,
string buf;
getline(ifs, buf);
方法4:
char c;
while((c = ifs.get())!= EOF) // 一个字符一个字符的读, 比较慢,
写二进制数据
person p = {"张三", 18};
ofs.write((const char *)&p, sizeof(p));
读二进制数据
person p
ofs.read((char *)&p, sizeof(person))
判断文件是否存在
char ch
ofs>>ch 读取一个字符
if(ofs.eof()) // eof函数查看最后一次读文件操作是否为文件最后一个记录,如果是,则返回非零值,如果文件还有内容,返回零。
{
// 文件为空
}
在创建输出流对象的时候可以直接指定 操作文件的路径以及文件操作的方式
ofstream ofs(“person.txt”, ios::out | ios::binary) ; 文件输出, 二进制方式操作, 其实就是创建对象的时候, 给默认构造函数传参
这样就省略了
ofs.open 这一步骤
类和对象, 对象特性, 成员变量和成员哈哈苏分开存储
只有非静态成员变量属于类的对象上,
其他的, 比如静态成员变量, 非静态成员函数, 静态成员函数, 都不属于类的对象上
多态:
多态分为两类,
静态多态: 函数重载和运算符重载属于静态多态, 复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别!
静态多态的函数地址早绑定, 编译阶段确定函数地址
动态多态的函数地址晚绑定, 运行阶段确定函数地址
动态多态满足条件
1 有继承关系
2 子类重写父类的虚函数
动态多态使用
父类的指针或引用, 执行子类对象
默认执行的是父类的成员函数(变量), 要想使用子类的成员函数或变量, 则需要父类的成员函数(变量) 添加virtual
重写: 函数返回值类型 ,函数名 ,参数列表, 完全一致称为重写
C++开发提倡利用多台涉及程序架构, 因为多态优点很多
在多态中,通常父类中虚函数的实现是毫无意义的, 主要是调用子类重写的内容
因此可以将虚函数改为纯虚函数:
纯虚函数语法: virtual 返回值 函数名 (参数列表) = 0;
当类中有了纯虚函数, 这个类也称为抽象类
抽象类特点:
(1)无法实例化对象
(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构和纯虚析构
1, 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2, 如果子类中没有堆区数据, 可以不写虚析构或纯虚析构
3, 拥有纯虚析构函数的类也属于抽象类
继承的好处, 可以减少重复的代码
class A: public B;
A类称为子类, 或派生类
B类称为父类,或基类
派生类中的成员, 包含两大部分
一类是基类继承过来的, 一类是自己增加的成员
从基类继承过来的表现其共性, 而新增的成员体现了其个性
子类以公有继承, 不可以访问继承父类私有数据, 公有权限数据继承后还是公有, 保护权限数据继承后还是保护权限
子类以保护继承, 不可以访问继承父类私有数据, 公有权限数据继承后变为保护, 保护权限数据继承后还是保护权限
子类以私有继承, 不可以访问继承父类私有数据, 公有权限数据继承后变为私有, 保护权限数据继承后变为私有权限
父类中所有非静态成员属性都会被子类继承下去, 父类中私有成员属性,是继承后被编译器给隐藏了.
继承中,创建子类也会同时创建出一个父类,(这样才能继承), 先调用父类构造函数, 在调用子类构造函数, 析构的顺序则为相反
子类对象可以直接访问到子类中同名成员
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数, 子类会隐藏父类中同名成员函数, 加作用域可以访问到父类中同名函数
同名的静态成员访问(处理)方式和非静态处理方式一样, 只不过有两种访问的方式(通过对象和通过类名)
多继承: 语法 class 子类 : 集成方式 父类1, 继承方式 父类2
class son : public base2, public base1 //
{
};
总结: 多继承中如果父类中出现了同名情况, 子类使用时候要加作用域!
菱形集成带来的主要问题是子类集成两份相同的数据, 导致资源浪费以及毫无意义
利用虚继承可以解决菱形继承问题.
class 父类
{
}
继承前加virtual关键字后, 变为虚继承
此时共用的父类称为虚基类
calss 子类1: virtual public 父类{}
calss 子类2: virtual public 父类{}
构造函数, 调用规则:
默认情况下, C++编译器至少给一个类添加3个函数
1, 默认构造函数(无参, 函数体为空)
2, 默认析构函数(无参, 函数体为空)
3, 默认拷贝构造函数, 对属性进行值拷贝
如果用户定义有参构造函数, C++编译器不再提供无参构造函数, 但是会提供默认拷贝构造函数
如果用户定义拷贝构造函数,C++不会再提供其他构造函数
运算符重载
通过成员函数重载+号运算符
返回值 operator+(参数)
{
重载的加号实现算法
}
左移运算符重载
不能利用成员函数重载<< 运算符, 因为无法实现cout 在左侧
ostream & operator<<(ostream &out, 类名&p)
{
}
自增运算符重载
myclass & operator++() // 前自增
{
xx++;
return *this
}
myclass operato++int()
{
myclass temp = *this;
return temp
}
前自增返回的是引用, 后自增返回的是值
C++ 类中:
静态成员变量和静态函数只有一份, 不论对象有多少个, 调用的都是同一份成员变量/成员函数
静态对象只能调用静态成员函数.
定义常函数
void test(int a) const // 在函数后面添加const 即为常函数
{
}
常函数不能修改成员变量, 要想修改成员变量, 成员变量需要添加 mutable 进行修饰
类外写成员函数:
构造函数
类名::类名()
{
}
其他函数
返回值 类名::函数名()
{
}
友员
在类中使用friend声明全局函数, 全局函数即可访问类中的私有属性(private)数据
在类中声明 友员中的类,也可以让友员类可以访问类中的private数据.
在类中声明 友员中的函数,也可以让友员类可以访问类中的private的数据.
class test1
{
friend void func2(void); // 声明全局函数作为友员
friend class test2; // 声明类作为友员
friend void test2::func1(void)// 声明类函数作为友员
private:
val1;
val2;
}
class test2
{
public:
void func1(void);
}
void func2(void)
{
}
函数占位参数:
C++中函数的形参列表里可以有占位参数, 用来做占位,调用函数时必须填补该位置
示例:
void func(int a, int)
{
cout << "this is func " << endl;
}
int main()
{
func(10, 10) // 占位参数必须填补
}
引用:
当函数的返回值是一个引用,那么这个函数可以作为左值,譬如
引用的本质就是起别名,实际的本质就是使用指针常量
int b = 20;
int &a = b; // 这里就是int * const a = &b, // 所以指针的指向是不能发生改变
a = 20; // 实际就是 * a = 20;// 编译器帮你进行处理
int & test()
{
static int a = 20;
return a;
}
int main()
{
test() = 1000; // 返回值作为引用,函数可以作为左值,
}
常量引用:
// 使用场景: 用来修饰形参, 防止误操作
const int &ref = 20; // 编译器将会将代码进行修改 int temp = 20; const int &ref = temp;不可以使用别名进行改变数值操作
int a = 200;
void test(const int &ref) // 修饰形参, 防止函数内部误操作
{
xxxx
}
int main(void)
{
test(a);
}
// 函数默认参数
// 如果我们自己给函数传入数据, 就用自己的数据, 如果没有, 那么就用默认值
// 语法: 返回值类型 函数名 (形参 = 默认值){}
// 注意事项:
// 1, 如果某个位置已经有了默认参数, 那么从这个位置往后, 从左到右都必须有默认值
// 2, 如果函数声明有默认参数, 函数实现就不能有默认参数, 如果函数实现有默认参数, 那么函数的声明就不能有默认参数
int func2(int a = 10, int b = 10); // 声明
int func2(int a = 10, int b = 10) // 这个是错误的
{
return a + b;
}
int func2(int a, int b) // 这个是正确的
{
return a + b;
}
函数的占位参数
C++中函数形参列表里面可以有占位参数, 用来做占位, 调用函数时必须填补该位置.
// 占位参数 还可以有默认参数
语法: 返回值类型 函数 (数据类型){}
void func(int a, int) // 函数的定义
{
cout << "this is func" << end1;
}
func(10, 20); // 函数调用
函数重载概述
作用: 函数名可以相同, 提高复用性
函数重载满足条件
(1) 同一个作用域下
(2) 函数名称相同
(3)函数参数类型不同, 或者个数不同, 或者顺序不同
注意: 函数的返回值不可以作为函数重载的条件
// 函数重载的注意事项
1, 应用作为重载的条件
2, 函数重载碰到默认参数
C++ 面向对象的三大特征: 分别是 封装, 继承, 多态
万事万物皆为对象, 对象上有其属性和行为.
封装的意义:
将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
封装意义:
在设计类的时候, 属性和行为写在一起, 表现事物
封装:
1,把变量(属性)和函数(操作)合成一个整体,封装在一个类中
2, 对变量和函数进行访问控制
访问权限:
1, 在类的内部(作用域范围内), 没有访问权限之分, 所有成员可以相互访问
2, 在类的外部(作用域范围外),访问权限才有意义, public, private, protected
3, 在类的外部, 只有public修饰的成员才能被访问, 在没有涉及集成与派生时,private和protected是同等级的,外部不允许访问.
public 公有, 对象内部可访问, 对象外部 也可访问
protected 保护, 对象内部可访问, 对象外部不可访问
private 私有, 对象内部可访问, 对象外部不可访问
[struct 和 class 的区别]
class 默认权限访问权限为 private , struct 默认访问权限为public
成员属性设置为私有
优点1: 将所有成员设置为私有, 可以自己控制读写权限
优点2: 对于写权限, 我们可以检测数据的有效性
对象的初始化和清理
构造函数和析构函数(对应对象的初始化和对象的清理)
C++ 利用了构造函数和析构函数解决上述问题, 这两个函数将会被编译器自动调用, 完成对象初始化和清理工作,对象的初始化和清理工作时编译器强制我们做的事情, 因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数时空实现.
构造函数: 主要作用是在于创建对象时为对象的成员属性赋值, 构造函数由编译器自动调用, 无需手动调用
析构函数: 主要作用是在于对象销毁前系统自动调用, 执行一些清理工作
构造函数语法: 类名(){}
1, 构造函数, 没有返回值也不写void
2, 函数名称与类名相同
3, 构造函数可以有参数, 因此可以发生重载
4, 程序在调用对象的时候自动调用构造, 无需手动调用, 而且自会调用一次.
析构函数语法: ~类名(){}
1, 析构函数,没有返回值, 也不写void
2, 函数名称与类名相同, 在名称前加上符号~
3, 析构函数不可以有参数, 因此不可以发生重载
4, 程序在对象销毁前会自动调用析构, 无需手动调用, 而却只会调用一次.
构造函数函数分类以及调用
按照参数类型进行分类 : 分为无参构造(编译器默认的)和有参构造函数
按照类型分类: 普通构造和拷贝构造函数(赋值构造函数)
class person
{
person(){}; // 默认构造(无参构造)
person(int age){}; // 有参构造
person(const persion & a){} // 拷贝构造, 相当于传入另一个对象, 然后使用另个一个对象对本对象进行复制,
};
调用方法:
括号法
显示法
隐式转换法
括号法
person p1; // 默认构造函数调用
person p2(10); // 有参构造
person p3(p2); // 拷贝构造函数
// 注意事项
调用默认构造函数的时候, 不要加()
person p1();
因为加了(),编译器会认为是一个函数的声明, 不会认为在创建对象
显示法:
person p1; // 默认构造函数调用
person p2 = person(10); // 有参构造
person p3 = person(p2); // 拷贝构造函数
person(10) // 匿名对象 特点: 当前执行结束后,系统会立即回收掉匿名对象
注意事项: 不要利用拷贝构造函数初始化,匿名对象, 编译器会认为person(p3) == person(p3)
相当于创建了一个p3的对象, 然后又传入一个叫p3的对象, 因此对象名称重复
隐式转换法:
person p4 = 10; 相当于写了person p4 = person(10); 有参构造
person p5 = p4; // 相当于拷贝构造
构造函数调用规则
默认情况下, C++编译器至少给一类添加3个函数
1, 默认构造函数(无参, 函数体为空)
2, 默认析构函数(无参, 函数体为空)
3, 默认拷贝函数, 对属性进行值拷贝
构造函数调用规则如下,
如果有用户定义有参构造函数, C++ 不再提供默认无参构造函数, 但是会提供默认拷贝构造
如果用户定义有参构造函数, C++不会再提供其他构造函数
深拷贝与浅拷贝
浅拷贝: 简单的赋值拷贝操作(编译器提供的内容简单的赋值)
但是浅拷贝会带来一些问题, 就是堆区的内存重复释放, 浅拷贝的问题, 要利用深拷贝进行解决
深度拷贝就是自己实现一个拷贝构造函数,
初始化列表,
C++提供了初始化列表的语法, 用来初始化属性
语法: 构造函数():属性:(值1) ,属性2(值2)…{}
当类对象作为其他类成员的时候
构造函数, 是先构造其他类的成员, 然后再构造自身,
析构函数则相反, 先析构自身, 然后再析构其他类的成员
静态成员变量必须在类中声明, 在类外定义
静态数据成员不属于某个对象, 在位对象分配空间中不包括静态成员所包括成员所占的空间
静态数据成员可以通过类名或者对象来引用
class Person{
public:
//类的静态成员属性
static int sNum;
private:
static int sOther;
};
//类外初始化,初始化时不加static
int Person::sNum = 0; // 类外声明, 不可省略
int Person::sOther = 0; // 类外声明, 不可省略
int main(){
//1. 通过类名直接访问
Person::sNum = 100;
cout << "Person::sNum:" << Person::sNum << endl;
//2. 通过对象访问
Person p1, p2;
p1.sNum = 200;
cout << "p1.sNum:" << p1.sNum << endl;
cout << "p2.sNum:" << p2.sNum << endl;
system("pause");
return EXIT_SUCCESS;
}
静态成员:
静态成员就是在成员变量和成员函数前加上关键字static, 成为静态成员
静态成员分为:
静态成员变量,
所有对象共享同一份数据
在编译阶段分配内存
类内声明, 类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
(因为静态成员函数在内存中只有一份, 如果是调用非静态变量(内存中并非一份, 会伴随对象产生), 函数中就无法知道,到底是哪操作的哪个对象的变量)
静态成员函数也是由访问权限的.
访问静态成员函数访问方式有两种, 通过对象进行访问, 以及通过类名进行访问
class person
{
public:
static void func()
{
cout << "static void func 调用" << end1;
}
}
void fucn01(void)
{
person.p;
p.func(); // 通过对象进行访问
person::func(); // 通过类名进项访问
}
在C++中, 类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
// 空对象占1个字节的内存空间
// C++ 编译器会给每个空对象分配一个字节空间, 是为了区分空对象占内存位置
// 每个空对象也应该有一个独一无二的内存地址
每一个非静态成员函数只会诞生一份函数实例, 也就是说每个同类型的对象会共用一块代码, 那么这块代码是如何区分哪个对象调用自己的?
C++,通过提供特殊的对象, this 指针, 解决上诉问题, this 指针指向被调用的成员函数所属的对象
this 指针是隐含每一个非静态成员函数内的一种之指针
this指针不需要定义, 直接使用即可
this 指针的用途
当形参和成员变量同名时,可用this 指针来区分
在类的非静态成员函数中返回对象本身, 可使用return *this
this 指针永远指向当前对象
this 指针的本质: 是指针常量, 指针的指向是不可以修改的.
也就是当我们 类名, p 的时候, 其实内部已经有一个this 的一个指针, 这个指针指向的就是这个对象, 不可以在内部进行修改.
空指针访问成员函数
空指针是可以访问成员函数的, 但是成员函数里面不能带有成员变量, , 但是尽量还是不要使用空指针访问成员函数
const 修饰成员函数
常函数:
成员函数后加const 后我们成为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable 后, 在常函数中依然可以修改.
常对象:
声明对象前加const 称该对象为常对象
常函数之只能调用常函数
在成员函数后面加const ,修饰的是this(指针的指向是不可以修改, 但是指针指向的值是可以修改的.) 指向, 让指针指向的值也不可以修改.
友元语法:
friend 关键字只出现在声明处
其他类: 类成员函数, 全局函数都可声明为友元
友元函数不是类的成员, 不带this指针
友元函数可访问对象任意成员属性, 包括私有属性
运算符重载:
运算符重载意义: 对已有的运算符重新进行定义, 赋予其另外一种功能, 以便适应不同的数据类型.
具体函数的语法:
operation+ // 加法运算符重载
当使用基类指针, 指向子类的对象的时候, 就需要积累的析构函数时虚析构函数, 因为, 如果不是虚析构函数, 当对象进行释放的时候,
只会调用基类的析构函数, 不会调用子类的虚构函数, 所以, 基类的虚构函数, 必须是虚析构函数,