常量
//宏常量
#define MONTH 30
//const
const int DAY = 7;
函数的分文件编写
1,创建后缀名为.h的头文件
2,创建后缀名为.cpp的源文件
3,在头文件中写函数的声明
4,在源文件中写函数的定义
//swap.h文件
#include <iostream>
using namespace std;
void swap(int a,int b);
//swap.cpp文件
#include "../头文件/swap.h"
void swap(int a,int b){
cout << a << endl;
cout << b << endl;
}
//调用文件
#include <iostream>
#include "源文件/swap.cpp"
using namespace std;
int main(){
swap(1,3);
return 0;
}
指针
指针的作用:通过指针可以间接访问内存
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址
1,定义指针
数据类型* 指针变量
int a = 10;
int *p;
//&取址符
p=&a;
//例如p的值为0x7ffee249e148
2,使用指针
通过解引用的方式来找到指针指向的内存
//指针前加一个*就是指针解引用,找到指针指向内存中的数据
cout << *p << endl;
//10
空指针
定义:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:内存编号为0~255的内存空间是系统占用内存,是不允许用户访问的
野指针
定义:指针变量指向非法的内存空间
例如
//指针变量p指向0x1100的地址会报错:非法访问权限
int *p = (int *)0x1100;
注意:在程序中避免出现野指针
const 修饰指针
const 修饰常量
int * const p = &a;
特点:指针的指向不可以更改,指针指向的值可以更改
const 修饰指针
const int * p = &a;
特点:指针的指向可以更改,指针指向的值不可以更改
const 既修饰指针又修饰常量
const int * const p = &a;
特点:指针的指向和指针的值都不可以更改
指针和数组
int arr[] = {1,2,3,4,5,6,7};
int * p = arr;
cout << *p << endl;
//打印结果:1
//数组名本身就是数组中第一个元素的地址
cout << p++ << endl;
//打印结果:2
//p++指针后移四个字节到数组的第二个元素
指针和函数
void swap(int a,int b){
int temp = a;
a=b;
b=temp;
}
int a = 10;
int b = 20;
//值传递
swap(a,b)
cout << "a=" << a << ";b=" << b << endl;
//打印结果:a=10;b=20
void swap(int *a,int *b){
int temp = *a;
*a=*b;
*b=temp;
}
int a = 10;
int b = 20;
//地址传递
swap(&a,&b)
cout << "a=" << a << ";b=" << b << endl;
//打印结果:a=20;b=10
//指针作为参数传递给函数可以改变实参的值,因为指针作为参数传递给函数时改变的是实参具体的内存地址
结构体
概念:结构体属于用户自定义的类型,允许用户存储不同的数据类型
//结构体定义时struct不可省略
struct Student {
int age;
string name;
}st3//在定义结构体时顺便创建结构体变量st3
创建结构体对象
//struct可以不写
struct Student st1;
st1.name = "张三";
st1.age = 20;
//struct可以不写
struct Student st2 = {"张三",20}
创建结构体数组
Student arr[] = {
{"张三",20},
{"李四",23},
}
结构体指针
作用:通过指针访问结构体中的的成员
利用操作符->可以通过结构体指针访问结构体属性
1,创建结构体变量
Student st1 = {"张三",20}
2,通过指针指向结构体变量
Student * p = &st1;
3,通过指针访问结构体变量中的数据
cout << p->name << endl;
>>输出结果:张三
结构体中const的使用场景
//使用
//防止误操作,将参数变成只读
void printStudent(const Student *st1){
cout << st1->name <<endl;
}
内存模型
利用new关键字可以将数据开辟到堆区
堆区的数据由开发员负责管理释放
堆去
//new 关键字创建数据并返回内存地址
int *p = new int(10);
指针的本质也是局部变量,放在栈上,指针保存的数据放在堆区
C++中的引用
引用做函数的返回值
//不要返回局部变量的引用
int& swap(){
int a = 10;
return a;
}
int &ref = swap();
cout << ref << endl;//第一次结果正确,因为编译器做了一次保留
cout << ref << endl;//第二次结果错误,因为局部变量a的内存已经释放
//函数的调用可以作为左值
int& swap(){
static int a = 10;//a变量的内存不会在调用结束后释放,而是整个程序运行结束才会释放
return a;
}
int &ref = swap();
cout << ref << endl;
cout << ref << endl;
>>所以两次都会打印正确的结果
swap() = 1000;
cout << ref << endl;
>> 打印结果:1000//因为swap()返回的是ref的引用,相当于给ref的引用赋值1000
引用的本质
引用的本质就是在C++内部实现一个指针常量
int a = 10;
//这句代码相当于 int* const ref = &a,指针常量是指针指向不可更改,也说明为什么引用不可更改
int& ref = a;
ref = 20;//内部发现ref是引用,自动帮我们转换为 *ref = 20
>> 引用半身需要一个合法的内存空间,因此下面这行代码错误
int& ref = 10;
函数高级
函数的默认参数
//函数形参列表中的形参是可以有默认值的
int func(int a=10,int b,int c = 20){}
//如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
int func(int a,int b=10,int c,int d){}//会报错
//如果函数声明的时候形参有默认值,函数实现的时候形参就不能有默认值
函数的重载注意事项
void func(int a){}
void func(const int a){}
int a=10;
func(a)//调用的是第一个函数
func(10)//调用的是第二个函数
>>因为a是变量,可读可写,10是常数
类和对象
C++面向对象的三大特性:封装,继承,多态
C++的世界中万物皆为对象,对象上有其属性和行为
封装
封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
const double PI = 3.14;
class Circle{
//访问权限
public:
//半径
int m_r;
//行为
//获取圆的周长
double calculateZC(){
return 2*PI*m_r;
}
}
//实例化圆类
Circle circle;
//给圆的半径赋值
circle.m_r = 10;
//获取周长
double zc = circle.calculateZC();
封装的访问权限
- public 公共权限
- protected 保护权限
- private 私有权限
class 和 struct的区别
- struct 默认权限为公共
- class 默认权限为私有
成员属性私有化
优点:
- 将成员属性设置为私有,可以自己控制读写权限
- 对于写权限,我们可以检测数据的有效性
#include <iostream>
using namespace std;
class Person{
private:
int _age;
string _name;
public:
void setName(string name){
_name = name;
}
string getName(){
return _name;
}
};
对象特性
构造函数和清理
class Person{
private:
string _name;
//构造函数
Person(){
cout << "构造函数" << endl;
}
Person(string name){
_name = name;
cout << "构造函数" << endl;
}
//析构函数,对象在销毁前会自动调用析构函数,而且只会调用一次
~Person(){}
}
//创建函数的时候构造函数会自动调用,而且只调用一次
Person person;
函数的分类
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
class Person{
private:
int _age;
//拷贝构造函数写法
Person(const Person &person){
_age = person.age;
}
}
//调用
1.括号法
Person p1;//无参
Person p2(10);//有参
Person p3(p2);//拷贝
//注意事项:调用默认构造函数时不要加(),因为编译器会认为这是一个函数的声明,从而不会去创建对象
Person p1();
2.显示法
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
//单独调用会出现一个匿名对象,匿名对象特点:当前行执行结束后,系统会立即回收掉匿名对象
Person(10)
3.隐式转换法
Person p1 = 10;
Person p2 = p3;
拷贝构造函数的调用时机
void func1(Person p){}
void func(){
Person p;
//这里就会调用拷贝构造函数
//因为这里是值传递,值传递会拷贝一个临时变量
func1(p)
}
//函数返回对象
Person func3(){
Person p;
//这里返回的对象并不是p对象本身,p对象会在函数调用结束后就被回收,而return出去的p是系统拷贝出来的一个对象
return p;
}
构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数
- 默认析构函数
- 默认拷贝函数
构造函数调用规则如下:
- 如果用户定义了有参构造函数,那么C++不再提供默认无参构造,但是会提供默认拷贝构造函数
- 如果用户定义了拷贝构造函数,C++不会再提供其他构造函数
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
初始化列表
C++提供了初始化列表的语法,用来初始化属性
传统方式初始化
class Person{
private:
int _a;
int _b;
int _c;
Person(int a,int b,int c){
_a = a,
_b = b,
_c = c
}
}
初始化列表初始化属性
Person(int a,int b,int c):_a(a),_b(b),_c(c){}
类对象作为成员属性
class Person{
private:
int age;
Phone phone;
//Phone phone = _phoneName 隐式转换法
Person(int _age,string _phoneName):age(_age),phone(_phoneNmae){}
}
class Phone{
private:
string phoneName;
Phone(string _phoneName):phoneName(_phoneName){}
}
Person person("张三","华为Mate40")
>>构造顺序:先构造Phone,再构造Person
静态成员
静态成员就是成员变量和成员函数前加上关键字static,成为静态成员
静态成员分为:
- 静态成员变量
- 静态成员函数
C++ 对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
this指针概念
既然在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上,那么也就是说每一个非静态成员函数只会诞生一份函数实例,多个同类型的对象会共用一块代码,那么这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决这个问题,this指针指向被调用的成员函数所属的对象
this指针是隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名是,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person{
private:
int age;
Person(int age){
this->age = age;
}
Person& addAge(int age){
this->age += age
return *this;
}
}
Person p2(10);
//链式调用
p2.addAge(20).addAge(30).addAge(40);
cout << p2.age << endl;
>>输出:100
//如果改成值返回,不是引用返回的话上面打印结果将是10,因为返回值而不是返回引用就会生成一份新的Person对象而不是p2对象本身了,所以后面调用再多的addAge也只是生成了一个新的临时变量不会影响p2本身,采用引用返回才是返回Person p2对象的本身
Person addAge(int age){
this->age += age
return *this;
}
const 修饰成员函数
常函数:
- 成员函数后加const后我们成为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
class Person{
private:
int age;
mutable string name;
void showPerson() const{
//this->age = 100;//成员函数后面加const就相当于const Person * const this;让指针指向的值也无法修改
//this = NULL;//this指针的本质是指针常量,指针常量的指向不可更改
this->name = "张三"//特殊变量,即使在常函数中,也可以修改这个值
}
string showName(){
return name;
}
}
void func(){
const Person p;//在对象前面加const变为常对象
p.name = "张三"
p.showName();//会报错
p.showPerson();//不会报错
//常对象只能调用常函数,不可调用普通函数
}
友元
友元的目的就是让一个函数或者一个类访问另一个类中的私有成员
友元关键字 friend
友元的三种实现方式
- 全局函数做友元
- 类做友元
- 成员函数做友元
全局函数做友元
class Building{
private:
string bedRoom;//卧室
public:
string sittingRoom;//客厅
Building(){
this->bedRoom = "次卧"
this->sittingRoom = "前展厅"
}
}
void goodFriend(Building *building){
cout << building->bedRoom << endl;//会报错,因为没有私有成员访问权限
cout << building->sittingRoom << endl;
}
void func(){
Building build;
goodFriend(&build);
}
class Building{
//将goodFriend全局函数作为Building的友元函数,那么goodFriend就可以访问Building中的私有成员变量了
friend void goodFriend(Building *building);
}
类做友元
class Building{
public:
string sittingRoom;
private:
string bedRoom;
}
class GoodFriend{
public:
void visit();
Building * building;
}
//类外写成员函数
Building::Building(){
sittingRoom = "前展厅";
bedRoom = "主卧";
}
GoodFriend::GoodFriend(){
//创建一个Building对象
building = new Building;
}
void GoodFriend::visit(){
cout << "好朋友正在访问" << building->sittingRoom << endl;
cout << "好朋友正在访问" << building->bedRoom << endl;//不做友元处理将无法访问
}
void func(){
GoodFriend goodFriend;
goodFriend.visit();
>>输出结果:好朋友正在访问前展厅
}
class Building{
//将GoodFriend作为Building的友元类
friend class GoodFriend;
}
void func(){
GoodFriend goodFriend;
goodFriend.visit();
>>输出结果:
好朋友正在访问前展厅
好朋友正在访问主卧
}
成员函数做友元
class Building{
//将GoodFriend的visit函数作为Building的友元函数可以访问Building的私有成员变量
friend void GoodFriend::visit();
public:
string sittingRoom;
private:
string bedRoom;
}
C++运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
class Person{
int age;
Person operator+(Person &p);
}
//给Person的成员函数operator+写上逻辑赋予加号新的意义
Person Person::operator+(Person &p){
Person p1;
p1.age = this->age+p.age;
return p1;
}
Person p1(10);
Person p2(30);
Person p3 = p1.operator+(p2);
简化为
Person p3 = p1+p2;
cout << p3.age << endl;
>>打印结果为:40
左移运算符重载
class Person{
public:
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧 cout << p;
void operator<<(cout){
}
int age;
string name;
Person();
Person(int age,string name);
}
Person::Person(int age,string name){
this->age = age;
this->name = name;
}
Person::Person(){
}
//只能利用全局函数重载左移运算符
ostream Person::operator<<(ostream &cout,Person &p){
cout << p.age << p.name;
return cout;
}
Person p(10,"张三");
cout << p << endl;//如果不返回cout对象的话将无法使用endl
>>打印结果:10张三
递增运算符重载
>>"前置递增"
class CustomInteger{
public:
int number;
CustomInteger(int number){
this->number = number;
}
CustomInteger& operator++();
}
CustomInteger& CustomInteger::operator++(){
this->number++;
return *this;
}
//这里注意:如果operator++返回的不是引用而是值
CustomInteger cus(0);
cout << ++(++cus) << endl;//那么这行会打印 2,
cout << cus << endl;//这一行会打印1,因为++cus返回的是一个临时变量,后续的++操作将不会对原来的cus对象有作用,如果是引用返回则 cus的值将是2
>>"后置递增"
class CustomInteger{
public:
int number;
CustomInteger(int number){
this->number = number;
}
CustomInteger operator++(int);
}
CustomInteger CustomInteger::operator++(int){
//用临时变量先记录
CustomInteger cus = *this;
//后运算
this->number++;
//将先前记录的临时变量返回
return cus;
}
>>**注意:后置递增一定返回值,因为临时变量在函数调用结束之后就被回收了,如果返回引用,则临时变量被回收之后指针会被释放掉,则将无法对指针的指向值进行操作**
赋值运算符重载
class Person{
public:
int *age;
Person(int age){
this->age = new int(age)
}
~Person(){
if(age !=NULL){
delete age;
age = NULL;
}
}
}
void func(){
Person p1(10);
Person p2(20);
p1 = p2;
//会报错,因为直接进行赋值运算,会将p1和p2的age指向同一个内存地址
//在程序结束的时候在析构函数中进行内存回收的时候就会报错,因为重复释放了
//那么如何解决重复释放的问题呢?下面我们来看看
}
//自己写一个赋值运算
void operator=(Person &p){
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if(age!=NULL){
delete age;
age = NULL;
}
//重新在堆区开辟一块内存赋值给age(深拷贝)
age = new int(*p.age);
}
//这样就不会报错了
//但是无法进行下面的运算,因为赋值方法返回的是void
void func(){
Person p1(10);
Person p2(20);
Person p3(30);
p3 = p2 = p1;
}
//所以改动如下
Person& operator=(Person &p){
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if(age!=NULL){
delete age;
age = NULL;
}
//重新在堆区开辟一块内存赋值给age(深拷贝)
age = new int(*p.age);
return *this;
}
关系运算符重载
作用:可以让两个自定义类型对象进行对比操作
class Person{
public:
int age;
string name;
Person(int _age,int _name){
age = _age;
name = _name;
}
bool operator==(Person &p){
return age == p.name && name == p.name;
}
bool operator!=(Person &p){
return age != p.name || name != p.name;
}
}
Person p1(18,"张三");
Person p2(18,"张三");
cout << p1 == p2 << endl;
cout << p1 != p2 << endl;
打印结果:
true
false
函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此成为仿函数
- 仿函数没有固定写法,非常的灵活
class Person(){
void operator(string text){
cout << text << endl;
}
}
Person p;
//由于调用起来非常类似于函数的调用,因此成为仿函数
p("Hello World");
>>打印结果:Hello World
cout << Person()() <<endl;//匿名函数对象
>>打印结果:Hello World
继承
面向对象的三大特性之一
类与类之间存在特殊的关系,定义这些类时下级别的成员除了拥有上一级的共性,还有自己的特性,这个时候我们就可以考虑利用继承的技术,减少重复性的代码
基本语法
class Person{}
class Student : public Person{}
继承的方式
- 公共继承
- 保护继承
- 私有继承
public > protected > private
继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
class Person{
public:
int a;
protected:
int b;
private:
int c;
}
class Student:public Person{
public:
int d;
}
Student st1;
//不管是什么权限的属性,子类都会继承一份,哪怕无法访问
cout << sizeof(st1) << endl;
>>打印结果:16
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
结论:继承中的构造函数顺序是从父类到子类,继承中的析构函数顺序是从子类到父类
继承同名成员处理方式
问题:当子类与父类出现同名成员时,如何通过子类对象,访问到子类或者父类中的同名数据呢?
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
class Person{
public:
string name;
Person(){
name = "张三";
}
string showName(){
return name;
}
string showName(string _name){
return _name;
}
}
class Student:public Person{
public:
string name;
Student(){
name = "李四";
}
string showName(){
return name;
}
}
Student st;
cout << st.name << endl;
cout << st.Person::name << endl;//添加作用域即可访问到父类中的同名数据
>>打印结果:
>李四
>张三
调用父类中的成员函数也是一样
cout << st.Person::showName() << endl;
st.showName("王五");//这里会报错
//因为如果子类中出现和父类同名的成员函数,那么子类的同名成员会隐藏掉父类中所有同名成员函数
//如果想要访问父类中的被隐藏的同名成员函数,需要加作用域
st.Person::showName("王五");
继承中同名静态成员处理方式
- 访问子类同名成员直接访问即可
- 访问父类同名成员,需要加作用域
继承语法
class Student:public Person1,public Person2{}
多继承中如果父类中出现了同名的情况,子类使用时需要加作用域
菱形继承问题
概念:两个派生类继承同一个基类,又有某个类同时继承着两个派生类,这种继承被称为菱形继承,或者钻石继承
当菱形继承,两个父类拥有相同数据,需要加以作用域区分
两份相同的数据会造成资源浪费和二义性的问题
那么如何解决呢?
虚拟继承
class A{
public:
int a;
}
class B:virtual public A{
public:
int b;
}
class C:virtual public A{
public:
int c;
}
class D:public B,public C{
public:
int d;
}
菱形虚继承后,基类的成员变量只拷贝了一份,解决了资源浪费的问题,但是并没有解决二义性的问题,多继承也是C++复杂的一个体现之一,也是C++的一个设计缺陷,一般情况下不建议设计出菱形继承逻辑
多态
面向对象的三大特性之一
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定-编译阶段确定函数地址
- 动态多态的函数地址晚绑定-运行阶段确定函数地址
多态的基本语法
class Animal{
void speek(){
cout << "动物在说话" << endl;
}
}
class Cat:public Animal{
void speek(){
cout << "猫在说话" << endl;
}
}
class Dog:public Animal{
void speek(){
cout << "狗在说话" << endl;
}
}
//地址早绑定,在编译阶段确定函数地址,所以不管传入谁,都是动物在说话
//如果想执行猫在说话,就不能让这个函数的地址提前绑定,需要在运行时进行绑定,地址晚绑定
void func(Animal &animal){
animal.speek();
}
Cat cat;
func(cat);
>>打印结果:动物在说话
class Animal{
virtual void speek(){
cout << "动物在说话" << endl;
}
}
Cat cat;
Dog dog;
func(cat);
func(dog);
>>打印结果:猫在说话
>狗在说话
动态多态的满足条件:
- 继承关系
- 子类要重写父类的虚函数 virtual(类似JAVA中的abstract函数)
动态多态的使用:父类指针或者引用执行子类对象
多态的原理剖析
Animal中定义虚函数时Animal 和 Cat 中都有一个vfptr(虚函数vftable指针表)
当子类重写父类的虚函数子类中的虚函数表内部会替换成子类的虚函数地址,当父类的指针或者引用指向子类对象时,发生多态
Animal &animal = cat;
animal.speek();
<<打印结果:猫在说话
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
当类中有了纯虚函数,这个类也称为抽象类
纯虚函数语法
virtual 返回值类型 函数名 (参数列表) = 0;
抽象类特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Person(){
virtual string getName() = 0;
}
class Student(){
virtual string getName(){
return "张三"
}
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
虚析构语法
virtual ~类名(){}
纯虚析构函数语法
virtual ~类名() = 0;
C++ 文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件
文件类型分为两种:
- 文本文件:文件以文本的ASCII码形式存储在计算机中
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
- ofstream:写操作
- ifstream:读操作
- fstream:读写操作
文本文件-写文件
#include <fstream>
//创建输出流对象
ofstream ofs;
//指定打开方式
ofs.open("test.txt",ios::out);
//写内容
ofs << "张三张三李四" << endl;//endl换行
//关闭输出流
ofs.close();
文本文件-读文件
//创建输入流对象
ifstream ifs;
//打开文件并判断是否打开成功
ifs.open("test.txt",ios::in);
if(!ifs.is_open()){
cout << "文件打开失败" <<endl;
return 0;
}
//读数据
//第一种
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;
}
//关闭输入流
ifs.close();
二进制文件-写文件
Student stu(20,"张三");
ofstream ofs("person.txt",ios::out|ios::binary);
ofs.write((const char*)&stu,sizeof(Person));
ofs.close();
二进制文件-读文件
ifstream ifs("person.txt",ios::in|ios::binary);
if(!ifs.is_open()){
cout << "打开文件失败" << endl;
return 0;
}
Student stu;
ifs.read((char *)&stu,sizeof(Student));
cout << "姓名:" <<stu.name << "年龄:" << stu.age << endl;
ifs.close();
模板
模板的概念
模板就是简历通用的模具,大大提高复用性
模板的基本语法
函数模板语法
建立一个通用函数,其函数返回值和形参类型可以不具体指定,用一个虚拟的类型来代表
template<typename T>
string swap(T &a){
return "张三"
}
或者
template<class T>
string swap(T &a){
return "张三"
}
类模板的基本语法
template<class A,class B>
class Person{
public:
A a;
B b;
Person(A a,B b){
this->a = a;
this->b = b;
}
};
普通函数与函数模板的区别
- 普通函数调用可以发生隐式类型转换
- 函数模板用自动类型推导不可以发生隐式类型转换
- 函数模板用显示指定类型可以发生隐式类型转换
普通函数和模板函数的调用规则
普通函数和函数模板是可以发生函数重载的
当普通函数和函数模板重名的时候,调用规则如下
- 如果函数模板和普通函数都可以实现,有限调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
//空模板参数列表来强制调用函数模板
func<>(a,b);
类模板与函数模板的区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建