zxf c++学习

02

回顾:
1 C++语言概述,C++98、C++11/C++0x
2 第一个C++程序
1)g++
2).cpp
3)头文件
#include
#include
4)cin、cout

3 名字空间/命名空间
1)定义
namespace 空间名{成员;…};
2)使用名字空间中的成员
–》空间名::成员
–》名字空间指令,using namespace std;
–》名字空间声明,using 空间名::成员;
3)全局作用域和无名名字空间
::成员;
4)名字空间嵌套

4 C++的结构体struct、联合体union、枚举enum

5 字符串string
1)定义
string str = “…”;
2)操作:
拷贝(=) 连接(+ +=) 比较(== != …)
随机访问([])
3)成员函数:
size()/length()
c_str()
========================================**
今天:
六 C++的布尔类型bool
1 bool类型是C++中的基本数据类型,专门表示逻辑值,使用true表示逻辑真,使用false表示逻辑假
2 bool类型在内存占一个字节:1表示true,0表示false
3 bool类型变量可以接收任何类型表达式结果,其值非零则为真,为0则为假。

七 操作符别名//了解
&& <> and
|| <
> or
^ <> xor
{ <
> <%
} <==> %>

八 C++的函数
1 函数重载
1)定义
在相同作用域中,定义同名的函数,但是它们参数必须要有所区分,这样的函数之间关系称为函数重载
注:函数重载和返回类型无关

eg:实现图形库
//C语言
void drawRect(int x,int y,int w,int h){…}
void drawCircle(int x,int y,int r){…}

//C++语言
void draw(int x,int y,int w,int h){…}
void draw(int x,int y,int r){…}

2)函数重载的匹配
调用重载关系的函数时,编译器会根据参数的匹配程度,自动选择最优的重载版本,当前g++编译器匹配的一般规则:
完全匹配>=常量转换>升级转换>降级转换>省略号匹配

3)函数重载原理
C++编译器是通过对函数进行换名,将参数表的类型信息整合到新的函数名中,解决函数重载和名字冲突的矛盾。

笔试题:C++中extern "C"声明的作用?
答案:可以在C++函数声明的前面加入extern “C”,要求C++编译器不会改函数进行换名,方便C程序调用改函数。

注:被extern "C"声明的函数无法重载

2 函数的缺省参数(默认实参)
1)定义函数时,可以为函数的参数指定缺省值,调用该函数时,如果不给实参,就取缺省值作为相应的实参.
void func(int a,int b,int flag = 0){}
2)靠右原则:如果一个参数有缺省值,那么该参数右侧的所有参数都必须带有缺省值。
3)如果函数的定义和声明分开,缺省参数应该写在函数的声明部分,而定义时不要再写.

3 函数的哑元参数
1)定义
定义函数时,只有类型而没有变量名的参数被称为是哑元
void func(int/哑元/){

}
2)使用哑元的场景
–》操作符重载时,用于区分前后++/–(后面讲)
–》为了兼容旧代码
eg:
算法库:void math_func(int a,int b){…}
使用者:
int main(void){
math_func(10,20);

math_func(30,40);

}
------------------------
升级算法库:void math_func(int a,int/哑元/){…}
使用者:
int main(void){
math_func(10,20);

math_func(30,40);

}

4 内联函数(inline)
1)定义
使用inline修饰的函数即为内联函数,编译器会尝试对内联函数进行优化,可以避免函数调用的开销,提高代码的执行效率.
inline void func(void){…}
2)使用说明
–》多次调用的小而简单的函数适合内联
–》调用次数极少或者大而复杂的函数不适合内联
–》递归函数不能内联
–》虚函数不能内联(后面讲)

注:内联只是一种建议而不是强制语法要求,一个函数能否进行内联优化主要取决于编译器,有些函数不加inline修饰也会默认处理为内联优化,有些函数即便加了inline修饰也会被编译器忽略掉。

九 C++的动态内存管理
1 回顾C中动态内存管理
1)分配:malloc()
2)释放:free()

2 C++通过操作符实现动态内存管理
1)分配:new/new[]
2)释放:delete/delete[]
eg:
int* pi = (int*)malloc(4);

free(pi);
---------------------
int* pi = new int;

delete pi;

十 C++的引用(reference)
1 定义
1)引用即别名,就是某个变量的别名,对引用的操作和对该变量本身完全相同.
2)语法
类型 & 引用名 = 变量名;
注:引用在定义时必须要初始化,而初始化以后其绑定的目标变量不能再做修改.
注:引用的类型与绑定的目标变量类型要一致

eg:
int a = 10;
int& b = a;//b就是a的别名
b++;
cout << a << endl;//11

2 常引用
1)定义引用时加const修饰,即为常引用
const 类型 & 引用名 = 变量名;
类型 const & 引用名 = 变量名;//和上面等价
2)不能通过常引用修改绑定的目标变量
eg:
int a = 10;
const int& b = a;//b是a的常引用
b++;//error
a++;//ok
cout << b << endl;//11

3)对于普通引用也可以称为左值引用,只能引用左值,而常引用也可以称为万能引用,既可以引用左值,也可以引用右值.

4)关于左值和右值
左值(lvalue):可以放在赋值操作符左侧,可以被修改
–》普通的变量
–》前++、–表达式结果
–》赋值表达式结果(= += -= …)

右值(rvalue):只能放在赋值操作符右侧,不能被修改
–》常量
–》多数运算表达式结果的临时变量
–》类型转换结果的临时变量
–》函数返回结果的临时变量
:引用型函数参数、引用型函数返回值
练习:关于引用和指针区别?//手机百度\

03

回顾:
1 bool类型
2 操作符别名
3 C++的函数
1)函数重载,extern “C”
2)缺省参数,void func(int a,int b=0){}
3)哑元参数,void func(int/哑元/){}
4)内联函数(inline)
4 动态内存分配
1)分配:new、new[]
2)释放:delete、delete[]
5 C++的引用
1)引用就是别名
类型 & 引用名 = 变量;
2)常引用
const 类型 & 引用名 = 变量;
3)扩展:关于左值(lvalue)和右值(rvalue)

今天:
十 引用(reference)

3 引用型函数参数
1)可以将引用用于函数的参数,这时形参就是实参的别名,通过形参可以直接修改实参变量的值,同时还可以避免传参的过程,减小函数调用开销,提高代码执行效率。
2)引用型函数参数有可能意外的修改实参值,如果不希望修改实参本身,可以将形参声明为常引用,提高传参效率的同时还可以接收常量型的实参。

4 引用型函数返回值
1)可以将函数的返回类型声明为引用,这时函数的返回结果就是return后面数据的别名,可以避免函数返回值的开销,提高代码的执行效率。
2)如果函数返回类型是左值引用,那么该函数的返回结果就也是一个左值。
int& func(void){
static int num = 100;
return num;
}
func() = 200;//ok
注:不要从函数中返回局部变量的引用,因为所引用的内存会在函数返回以后被释放,使用非常危险!可以返回成员变量、静态变量、全局变量的引用

5 引用和指针
1)如果从C语言角度看引用的实现,其本质就是指针;但是C++开发中尽量多使用引用而不是指针.
int i = 100;
int* const pi = &i;
int& ri = i;
*pi <=等价=> ri
2)指针可以不做初始化,其目标也可以在初始化再修改(指针常量除外);而引用定义时必须初始化,而且一旦初始化其引用的目标不能再修改.

3)可以定义指针的指针(二级指针),但是不能定义引用的指针//了解
4)可以定义指针的引用(指针变量的别名),但是不能定义引用的引用//了解
5)可以定义指针数组,但是不能定义引用数组,但是可以定义数组引用//了解
6)可以定义函数指针,也可以定义函数引用,其语法特性一致//了解

十一 类型转换
1 隐式类型转换
char c = ‘A’;
int i = c;//隐式


void func(int i){}
func©;//隐式


int func(void){
char c = ‘A’;
return c;//隐式
}

2 显式类型转换
2.1 C++兼容C中强制转化
char c = ‘A’;
int i = (int)c;//C风格强制转换
int i = int©;//C++风格强制转换
2.2 C++中扩展了四种操作符形式的显式类型转换
1)静态类型转换
语法:
目标变量=static_cast<目标类型>(源类型变量)
适用场景:
主要用于将void*转换为其它类型的指针.

2)动态类型转换(后面讲)
语法:
目标变量=dynamic_cast<目标类型>(源类型变量)

3)常类型转换
语法:
目标变量=const_cast<目标类型>(源类型变量)
适用场景:
主要用于除去指针或引用的常属性

4)重解释类型转换
语法:
目标变量=reinterpret_cast<目标类型>(源类型变量)
适用场景:
–》任意类型的指针或引用之间的显式转换
–》在指针和整型数之间的显式转换
eg:向物理内存0x12345678,保存整型数据100?
int* paddr = reinterpret_cast(0x12345678);
*paddr = 100;

小结:来自C++社区给C程序员的建议
1 慎用宏,可以通过const、enum、inline来替换
#define PAI 3.14
==> const double PAI = 3.14;
#define STATE_SLEEP 0
#define STATE_RUN 1
#define STATE_STOP 2
==> enum STATE{SLEEP,RUN,STOP};
#define max(a,b) ((a)>(b)?(a):(b))
==> inline int max(int a,int b){
return a > b ? a : b;
}
2 变量应该随用随声明同时初始化
3 尽量使用new/delete替换malloc/free
4 少用void*、指针计算、联合体、强制转换
5 尽量使用string表示字符串,少用C风格的char*


十二 类和对象 //了解
1 什么是对象
万物皆对象,任何一种事物都可以看做是对象。

2 如何描述对象
通过对象的属性(名词、数量词、形容词)和行为(动词)来描述对象

3 面向对象的程序设计
对自然世界中的观察引入到编程实践中的中理念和方法,这种方法称为"数据抽象",即描述对象时把细节东西剥离出去,只考虑一般性的、有规律性的和统一性的东西

4 类
类是将多个对象共性提取出来定义的一种新的数据类型,是对 对象属性和行为的抽象描述。

现实世界 类 虚拟世界
具体对象–抽象–》属性/行为–实例化–》具体对象

十三 类定义与实例化
1 类的一般语法形式
struct/class 类名:继承方式 基类,…{
访问控制限定符:
类名(形参表):初始化列表{}//构造函数
~类名(void){}//析构函数
返回类型 函数名(形参表){}//成员函数
数据类型 变量名;//成员变量
};
2 访问控制限定符
1)public:公有成员,任何位置都可以访问
2)private:私有成员,只有类内部的成员函数才能访问
3)protected:保护成员(后面讲)
注:如果没有显式指定访问控制属性,使用struct定义类默认的访问控制属性是public,而class定义类默认访问控制属性是private.
struct/class XX{
int a;//公有,私有
public:
int b;//公有成员
int c;//公有成员
private:
int d;//私有成员
public:
int e;//公有成员
};

扩展练习V1.0:企业员工管理系统
需求:实现表示企业员工的类
工资计算=基本工资+绩效工资;
基本工资=薪资*出勤率(输入出勤天数/23.0)
绩效工资=基本工资/2;
工号:必须大于10000
姓名:字符串长度小于20字符
薪资:大于0
提示:
class Employee{
//行为:打印员工信息、计算工资
//属性:工号,姓名,薪资

04

回顾:
1 引用
1)引用型函数参数
2)引用型函数返回值
3)引用和指针

2 类型转换
1)静态类型转换static_cast
2)常类型转换const_cast
3)重解释类型转换reinterpret_cast

3 类和对象//了解

今天:
十三 类定义与实例化
1 类的一般语法形式
struct/class 类名:继承方式 基类,…{
访问控制限定符:
类名(形参表):初始化列表{}//构造函数
~类名(void){}//析构函数
返回类型 函数名(形参表){}//成员函数
数据类型 变量名;//成员变量
};
2 访问控制限定符
1)public:公有成员,任何位置都可以访问
2)private:私有成员,只有类内部的成员函数才能访问
3)protected:保护成员(后面讲)

3 构造函数(constructor)
1)语法
class 类名{
类名(形参表){
主要负责对象的初始化(初始化成员变量);
}
};
2)函数名和类名一致,没有返回类型
3)构造函数在创建对象时自动被调用,不能像普通函数一样显式的调用
4)在每个对象的生命周期,构造函数一定会被调用,但仅会被调用一次

eg:实现一个电子时钟类,使用构造函数初始化当前时间为系统时间,以秒为单位运行.
class Clock{
public:
Clock(time_t t){
tm* local = localtime(&t);
时 = local->tm_hour;
分 = local->tm_min;
秒 = local->tm_sec;
}
void run(void){
while(1){打印时间;计时+1s;sleep(1)}
}
private:
int 时,分,秒;
};
Clock c(time(NULL));
c.run();

4 对象的创建和销毁
1)栈区创建单个对象 //重点掌握
类名 对象(构造实参表);//直接初始化
类名 对象 = 类名(构造实参表);//拷贝初始化,实际初始化过程和上面一样.

2)栈区创建多个对象(对象数组)
类名 对象数组[元素个数] = {类名(构造实参表),…};

3)在堆区创建/销毁单个对象 //重点掌握
创建:类名* 对象指针 = new 类名(构造实参表);
注:new对象时,首先会在堆区分配内存,然后会自动调用构造函数,完成对象的创建;而如果是malloc仅能完成内存分配,不会调用构造函数,所以malloc不具备创建对象的能力。
销毁:delete 对象指针;

4)在堆区创建/销毁多个对象
创建: 类名* 对象指针 =
new 类名[元素个数]{类名(构造实参表),…};
销毁: delete[] 对象指针;

5 多文件编程:类声明和类的实现可以放在不同的文件中
1)类的声明放在头文件中:xx.h
2)类的实现放在源文件中:xx.cpp

十四 构造函数和初始化列表
1 构造函数可以重载,也可以带有缺省参数
2 缺省(无参)构造函数
1)如果类中没有定义任何构造函数,编译器会为该类一个无参的构造,即为缺省构造函数:
–>对于基本类型的成员变量,不做初始化.
–>对于类类型的成员变量(成员子对象),将自动调用相应类的无参构造函数来初始化.
2)如果自己定义了构造函数,无论是否有参数,编译器都不会再提供缺省构造函数了.

3 类型转换构造函数(单参构造函数)
class 类名{
[explicit] 类名(源类型){…}
};
可以实现源类型变量隐式转换成当前类类型的对象。
注:可以使用explicit修饰类型转换构造函数,可以强制要求通过它实现类型转换的功能必须显式完成.

4 拷贝(复制)构造函数
1)用一个已存在的对象构造同类型的副本对象时,会调用该类的拷贝构造函数。
class 类名{
类名(const 类名&){…}
};
2)如果一个类没有定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数:
–>对于基本类型成员变量,按字节复制
–>对于类类型成员变量(成员子对象),将自动调用相应类的拷贝构造函数来初始化。
注:实际开发中,一般不需要自定义拷贝构造函数,因为编译器缺省提供的拷贝构造函数已经很好用了.

3)拷贝构造函数调用时机
–》用已定义的对象作为同类型对象的构造实参
–》以对象形式向函数传递参数
–》从函数中返回对象(有时会被编译器优化掉)

5 初始化列表
1)语法
class 类名{
类名(形参表)
:成员变量1(初值1),成员变量2(初值),…{}
};
2)多数情况下使用初始化列表和在构造函数体中赋初值没有太大区别,两种写法可以任选,但是有些特殊场景必须要使用初始化列表:
–>如果有类类型的成员变量(成员子对象),并且希望以有参的方式来初始化(比如有些类没有无参构造),则必须要使用初始化列表来初始化.

练习:为电子时钟类增加计时器功能,如果用系统时间构造对象,表现为时钟;如果以无参的方式创建对象,使用初始化列表将时间初始化0,则表现为计时器功能.

练习:复习当天语法

扩展练习V2.0:企业员工管理系统
需求:优化企业员工类
1)增加构造函数(无参,有参)
2)创建员工对象(栈区,堆区)
3)将类的声明和定义分开
*4)尝试编写Makefile(make)
《跟我一起写Makefile》–陈皓

05

回顾:
1 类的定义和实例化
1)访问控制限定符:public private
2)构造函数
class 类名{
类名(形参表){…}
};
3)对象创建/销毁
栈区:
类名 对象(构造实参表);
类名 对象 = 类名(构造实参表);
堆区:
类名* 对象指针 = new 类名(构造实参表);
delete 对象指针;
4)多文件编程
xx.h:
class XX{//类的声明
构造函数(形参表);
返回类型 函数名(形参表);
成员变量;
};
xx.cpp:
XX::构造函数(形参表);
返回类型 XX::函数名(形参表);

2 构造函数和初始化列表
1)构造函数可以重载、可以带有缺省参数
2)缺省(无参)构造函数
3)类型转换构造函数(单参构造函数),explicit
4)拷贝构造函数
class 类名{
类名(const 类名&){…}
};
A a1(…);
A a2=a1;
5)初始化列表
class 类名{
类名(形参表):成员变量(初值),…{}
};

今天:
补充需要显式使用初始化列表的场景:
–》类中包含引用或const型的成员变量,必须要使用初始化列表来初始化.
–》成员变量的初始化顺序由声明顺序决定,而与初始化列表的顺序无关,所以不要使用一个成员变量去初始化另一个成员变量.


十五 this指针和常成员函数(常函数)
1 this指针
1)类中的成员函数(包括构造函数、析构函数)中都隐藏一个当前类类型的指针参数,名为this,在成员函数中访问类中的其它成员,本质都是通过this来实现的:
–》对于普通的成员函数,this指向调用该函数的对象
–》对于构造函数,this指向正在创建的对象
class A{
public:
int m_data;
//void print(A* this)
void print(void){
//cout << this->m_data << endl
cout << m_data << endl;
}
};

2)需要显式使用this指针的场景
–》区分作用域
–》从成员函数中返回调用自身(返回自引用)//重点掌握
–》从类的内部销毁对象自身(对象自销毁)
–》作为成员函数的实参,实现对象间交互//了解

2 常成员函数
1)在一个成员函数参数表后面加const修改,即为常成员函数函数:
返回类型 函数名(形参表) const {…}
2)常成员函数中this是一个常量指针,不能在常函数中修改成员变量的值
注:被mutable关键字修饰的成员变量,可以在常函数中直接修改。

3)非const对象既可以调用常函数也可以调用非常函数,但是常对象只能调用常函数,不能调用非常函数.//重要
注:常对象也包括常指针和常引用

4)同一个类中,函数名和形参表相同的成员函数,其常版本和非常版本可以构成重载关系,常对象匹配常版本,非常对象匹配非常版本.

十六 析构函数(Destructor)
1 语法
class 类名{
~类名(void){
//主要负责清理对象生命周期中的动态资源
}
};
1)函数名一定"~类名"
2)没有返回类型,也没有参数
3)析构函数不能重载,一个类只能有一个析构函数

2 当对象被销毁时,该类的析构函数自动被调用和执行
–》栈对象,离开所在作用域时,其析构函数被作用域终止的右花括号调用.
–》堆对象,其析构函数被delete操作符调用

3 如果类中没有显式定义析构函数,那么编译器会为该类提供一个缺省析构函数:
1)对于基本类型的成员变量什么也不做
2)对于类类型的成员变量(成员子对象),会自动调用相应类析构函数

4 对象的创建和销毁的过程
1)创建过程
–》分配内存
–》构造成员子对象(按声明顺序)
–》执行构造函数代码

2)销毁过程
–》执行析构函数代码
–》析构成员子对象(按声明逆序)
–》释放内存

十七 拷贝构造和拷贝赋值
1 浅拷贝和深拷贝
1)如果类中包含指针形式的成员变量,缺省的拷贝构造函数只是复制了指针变量本身,没有复制指针所指向的内容,这种拷贝方式称为浅拷贝.
2)浅拷贝将导致不同对象间的数据共享,如果数据在堆区,析构时还可能会出现“double free”的错误,导致进程终止,所以必须自己定义一个支持复制指针所指向内容的拷贝构造函数,即深拷贝.

练习:实现String类,其构造函数支持C风格的const char字符串,并为其提供析构函数、拷贝构造函数
提示:
class String{
public:
String(const char
str){
m_str = new char[strlen(str)+1];
strcpy(m_str,str);
}
//析构函数
//拷贝构造
private:
char* m_str;
};

String s = “hello”

练习:复习当天内容

扩展练习V3.0:企业员工管理系统
需求:优化员工类
1)构造函数使用初始化列表改写
2)打印信息函数改为常成员函数
3)在创建对象时,把员工信息保存到文件中,文件名和员工的工号保持一致
提示:
class Employee{
Employee(int id,…):m_id(id){
m_id(int)–>m_id(const char*)//sprintf
file = fopen(“m_id”,“w”);
fprintf(file,"%d …",m_id,…)
}
File* file;
};

06

回顾:
1 初始化列表
类名(形参表):成员变量(初值),…{…}
2 this指针
–》区分作用域
–》返回自引用
–》对象自销毁
–》作为实参,实现对象间交互

3 常成员函数(常函数)
返回类型 函数名(参数表) const {}
正常不能在常函数修改成员变量
但是被mutable关键字修饰成员变量可以修改

4 析构函数
~类名(void){清理对象的动态资源}

5 对象创建和销毁过程
创建:分配内存->构造成员子对象->执行构造函数代码
销毁:执行析构函数代码->析构成员子对象->释放内存


今天:
十七 拷贝构造和拷贝赋值
1 浅拷贝和深拷贝
1)如果类中包含指针形式的成员变量,缺省的拷贝构造函数只是复制了指针变量本身,没有复制指针所指向的内容,这种拷贝方式称为浅拷贝.
2)浅拷贝将导致不同对象间的数据共享,如果数据在堆区,析构时还可能会出现“double free”的错误,导致进程终止,所以必须自己定义一个支持复制指针所指向内容的拷贝构造函数,即深拷贝.

2 拷贝赋值
1)当两个对象进行赋值操作时,比如“i3=i2”,编译器会自动将其处理为“i3.operator=(i2)”成员函数调用形式,其中“operator=”被称为拷贝赋值函数,但其本质就是一个成员函数,该函数的返回结果就是表达的结果.
2)如果类中包含了指针形式的成员变量,缺省的拷贝赋值函数和缺省拷贝类似,也是浅拷贝,可能有double free和内存泄漏的错误,因为必须自定义深拷贝赋值函数:
类名& operator=(const 类名& that){
if(&that != this){//防止自赋值
释放旧内存;
分配新内存;
拷贝新数据
}
return *this;//返回自引用
}
注:that对象对应右操作符,this指向左操作数
练习:实现String类,增加拷贝赋值函数

十八 静态成员(static)
1 静态成员变量
1)语法
class 类名{
static 数据类型 变量名;//声明
};
数据类型 类名::变量名=初值;//定义和初始化
2)使用说明
–》普通成员变量属于对象,每个对象都会包含一份独立的普通成员变量;而静态成员变量不属于对象,一个类中静态成员变量只有一份
–》静态成员变量不能在构造函数中定义和初始化,需要在类的外部单独的定义和初始化。
–》静态成员变量和全局变量类似,被放在数据段,可以把静态成员变量理解为被限制在类中使用的全局变量.
–》访问静态成员变量方法
类名::静态成员变量;//推荐
对象.静态成员变量;//本质和上面一样

2 静态成员函数
1)语法
class 类名{
static 返回类型 函数名(形参表){}
};
2)使用说明
–》静态成员函数中没有this,也没有const属性,可以把静态成员函数理解为被限制在类中使用的全局函数
–》使用方法:
类名::静态成员函数(实参表);//推荐
对象.静态成员函数(实参表);//本质和上面等价
–》在静态成员函数中只能访问静态成员,在非静态成员函数中既可以访问静态成员,也可以访问非静态成员

3 单例模式
1)概念
一个类只允许创建唯一的对象,并提供它的访问方法.
2)实现思路
–》禁止在类的外部创建对象:私有化构造函数
–》类的内部维护唯一的对象:静态成员变量
–》提供唯一对象的访问方法:静态成员函数
3)创建方式
–》饿汉式:单例对象无论用或不用,程序启动即创建
–》懒汉式:单例对象用时在创建,不用即销毁

十九 成员指针//了解
1 成员变量指针
1)定义
类型 类名::*成员指针变量名 = &类名::成员变量;
2)使用
对象.*成员指针变量名;
对象指针->*成员指针变量名;

注:"."被称为直接成员指针解引用操作符
"->
"被称为间接成员指针解引用操作符

2 成员函数指针
1)定义
返回类型 (类名::*成员函数指针)(参数表)
= &类名::成员函数名;
2)使用
(对象.*成员函数指针)(实参表);
(对象指针->*成员函数指针)(实参表);

二十 操作符(运算符)重载
1 基本概念
操作符重载的本质就是一个写具有特殊名称的函数,使用operator 后接一个操作符,比如“operator=” “operator+”,把已定义的符号来重新定义,实现程序员想要的运算功能.

eg:复数 x+yi
(1+2i) + (3+4i) = 4+6i

练习:复习当前内容,重点看String类实现,单例模式

扩展练习V4.0:企业员工管理系统
需求:优化员工类
1)禁止员工类拷贝操作
2)创建对象时,可以从指定文件中获取唯一的ID号
id.txt(10001)–>id+1–>id.txt(10002)
3)增加静态成员变量,保存企业员工的人数,并写入到指定文件(count.txt)
4)增加静态成员函数,用于获取员工人数

07

回顾:
1 拷贝构造和拷贝赋值
1)浅拷贝和深拷贝
2)拷贝赋值
a = b;//a.operator=(b)

2 静态成员
1)静态成员变量,数据段
2)静态成员函数,没有this指针

3 单例模式
–》私有化构造函数
–》静态成员变量在类的内部维护唯一对象
–》静态成员函数获取唯一的对象
–》创建方式:饿汉式、懒汉式

4 成员指针//了解
1)成员变量指针
2)成员函数指针


今天:
二十 操作符(运算符)重载
1 基本概念
操作符重载的本质就是一个写具有特殊名称的函数,使用operator 后接一个操作符,比如“operator=” “operator+”,把已定义的符号来重新定义,实现程序员想要的运算功能.

eg:复数 x+yi
(1+2i) + (3+4i) = 4+6i

2 双目操作符重载 L#R
2.1 计算类双目操作符:+ -
–》左右操作数既可以是左值也可以是右值
–》表达式的结果是右值
–》两种实现方式:
1)成员函数形式(左调右参)
L#R的表达式可以被编译器处理成L.operator#®成员函数调用形式,该函数返回结果就是表达式的结果。
2)全局函数形式(左右都参)
L#R的表达式也可以被编译器处理成operator#(L,R)全局函数调用形式,该函数返回结果就是表达式的结果。

注:两种实现方式只能选择一种,一般推荐成员函数形式
注:通过friend关键字可以把一个全局函数声明为某个类的友元,友元函数可以访问类中的任何成员.

2.2 赋值类双目操作符:+= -=
–》左操作数一定是左值,右操作数既可以是左值也可以是右值
–》表达式结果是左值,就是左操作数自身
–》两种实现方式:
1)成员函数形式:L#R ==> L.operator#®
2)全局函数形式:L#R ==> operator#(L,R)

3 单目操作符重载 #O
3.1 计算类单目操作符:- ~
–》操作数可以是左值也可以是右值
–》表达式的结果是右值
–》两种实现方式:
1)成员函数形式: #O ==> O.operator#();
2)全局函数形式: #O ==> operator#(O);

3.2 自增减单目操作符:++ –
1)前缀自增减
–》操作数必须是左值
–》表达式结果是左值,就是操作数的自身
–》两种实现方式:
成员函数形式: #O ==> O.operator#();
全局函数形式: #O ==> operator#(O);
2)后缀自增减
–》操作数必须是左值
–》表示结果是右值,操作数自增减之前副本
–》两种实现方式:
成员函数形式: O# ==> O.operator#(int/哑元/);
全局函数形式: O# ==> operator#(O,int/哑元/);

4 其它操作符重载
1)输出(<<)和输入(>>)操作符重载
功能:实现自定义类型对象的输出和输入
注:只能使用全局函数形式
friend ostream& operator<<(
ostream& os,const RIGHT& r){…}
friend istream& operator>>(
istream& is,RIGHT& r){…}

#include 
ostream //标准输出流类
istream //标准输入流类ostream cout;
istream cin;//全局 operator<<(cout,a)
cout << a;
cout << c ...//全局 operator>>(cin,a)
cin >> a;

2)下标操作符重载 []
功能:让对象可以像数组一样去使用
注:常对象返回时右值,非常对象返回左值

string s = “hello”;
s[0] = ‘H’;//s.operator = ‘H’

const string s = “hello”;
s[0] = ‘H’;//s.operator = ‘H’,应该error

练习:实现3*3矩阵,支持如下操作符+ -  += -= -(取负) ++、-- << * *= []1 2 3		9 8 7		10 10 104 5 6	+	6 5 4	=	10 10 107 8 9		3 2 1		10 10 101 2 3		9 8 7		-8 -6 -44 5 6	-	6 5 4	=	-2  0  27 8 9		3 2 1		 4  6  81 2 3		9 8 7		30  24  184 5 6	*	6 5 4	=	84  69  547 8 9		3 2 1		138 114 901 2 3	  -1 -2 -3-	4 5 6 = -4 -5 -6	7 8 9   -7 -8 -9 1 2 3m1 =  4 5 6	7 8 9m1[1][2] ==> 6

3)函数操作符 () //了解
功能:让对象可以当做函数来使用
注:对参数的个数、参数类型、返回类型没有任何限制
class A{};
A a1;
a1(100,3.14);//a1.operator()(100,3.14)

4)new/delete操作符 //了解
static void* operator new(size_t size){…}
static void operator delete(void* p){…}

5 操作符重载的限制
1)不能重载的操作符
–> 作用域限定操作符 “::”
–> 直接成员访问操作符 “.”
–> 直接成员指针解引用操作符 “.*”
–> 条件操作符 “?:”
–> 字节长度操作符 “sizeof”
–> 类型信息操作符 “typeid” //后面讲
2)如果一个操作符的所有操作数都是基本类型,无法重载
3)不能通过操作符重载的机制发明新的符号
4)操作符重载无法改变操作符的优先级
5)操作符重载不能改变操作数的个数
6)只能用成员函数实现的操作符
= () [] ->

练习:复习当天内容
扩展练习V5.0:企业员工管理系统
需求:优化员工类
1)增加输出(<<)操作符重载
2)增加比较(== !=)操作符重载
Employee emp(…);
//emp.operator==(10001)
if(emp == 10001){}

08

回顾:
1 双目操作符 L#R
L#R ==> L.operator#®
L#R ==> operator#(L,R)
2 单目操作符 #O
#O ==> O.operator#()
#O ==> operator#(O)
3 其它操作符
1)<< >>
cout << a; //operator<<(cout,a);
cin >> a;//operator>>(cin,a);
2)[]
string s=“hello”;
s[0];//s.operator
3)()
A a;
a(100,3.14);//a.operator()(100,3.14)
4)new/delete

今天:
二十一 继承(Inheritance)
1 继承的概念//了解
通过一种机制表达类型之间共性和特性的方式,利用已有的数据类型来定义新的数据类型,这种机制就是继承.
eg:
人类:姓名、年龄、吃饭、睡觉
学生类:姓名、年龄、吃饭、睡觉、学号、学习
教师类:姓名、年龄、吃饭、睡觉、工资、讲课

------------------------------------------
人类:姓名、年龄、吃饭、睡觉
学生类继承人类:学号、学习
教师类继承人类:工资、讲课

		人类(基类/父类)/	  \学生类	 教师类(子类/派生类)	基类--派生-->子类子类--继承-->基类

2 继承语法
class 子类:继承方式1 基类1,继承方式2 基类2,…{

};
继承方式:
–>public(公有继承)
–>protected(保护继承)
–>private(私有继承)

3 公有继承的语法特性
1)子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,如同是基类对象在访问它们一样。
注:子类对象中包含基类的部分被称为“基类子对象”

2)向上造型(upcast)//重点掌握
将子类类型指针或引用转换为基类类型指针或引用;这种操作性缩小的类型转换,在编译器看来是安全,可以直接隐式完成。
基类

子类

class A{};
class B:public A{};
class C:public A{};
class D:public A{};
...
void func(A* pa){...}
int main(void){A a;func(&a);B b;func(&b);C c;func(&c);D d;func(&d);
}

3)向下造型(downcast)
将基类类型的指针或引用转换为子类类型的指针或引用;这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,可以显式转换(推荐使用静态类型转换);
基类

子类

4)子类继承基类的成员:公有、保护、私有
–》通过子类,可以直接访问基类中的公有成员和保护成员,就如同它们被声明在子类中一样.
–》但基类中的私有成员,受到访问控制属性的影响,继承后不能直接访问,但是可以让基类提供公有或保护的接口函数来间接访问.
注:基类构造函数和析构函数子类无法继承。

5)子类隐藏基类的成员
–》如果子类和基类中定义了同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,当通过子类访问该成员时,将优先访问子类自己的成员。如果需要通过子类访问基类中被隐藏的成员,可以显式的通过"类名::"来指明。
–》如果形成隐藏关系的成员函数满足不同参的重载条件,也可以通过using声明,将基类中成员函数引入子类作用域,让它们形成有效的重载关系,通过重载匹配来解决//不推荐

4 继承方式和访问控制属性
1)访问控制限定符:影响访问该类的成员的位置
访问控制 访问控制 内部 子类 外部 友元
限定符 属性 访问 访问 访问 访问
public 公有成员 ok ok ok ok
protected 保护成员 ok ok no ok
private 私有成员 ok no no ok
2)继承方式:影响通过子类访问基类中成员的可访问性
基类中的 在公有子 在保护子 在私有子
类中变成 类中变成 类中变成
公有成员 公有成员 保护成员 私有成员
保护成员 保护成员 保护成员 私有成员
私有成员 私有成员 私有成员 私有成员

注:向上造型的语法在私有继承和保护继承中不再适用

class _A{
public:void func(void){..}
};
class A:private _A{};class B:public A{};
class C:public B{};
class D:public C{};
...

5 子类的构造函数
1)如果子类的构造函数没有显式指明基类子对象的初始化方式,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象。
2)如果希望基类子对象以有参的方式被初始化,则必须使用初始化列表。
class 基类{};
class 子类:public 基类{
//使用初始化列表指明基类子对象初始化方式
子类(…):基类(…){}
};
3)子类对象的创建过程
–》分配内存
–》构造基类子对象(按继承表顺序)
–》构造成员子对象(按声明顺序)
–》执行子类构造函数代码

6 子类的析构函数
1)子类的析构函数,无论自己定义的析构函数还是编译器缺省提供的,都会自动调用基类的析构函数,完成基类子对象的销毁。
2)子类对象的销毁过程
–》执行子类析构函数的代码
–》析构成员子对象(按声明逆序)
–》析构基类子对象(按继承表逆序)
–》释放内存

3)基类的析构函数不能自动调用子类的析构函数,所以对一个指向子类对象的基类指针使用delete操作符,实际被调用的仅是基类的析构函数,子类的析构函数执行不到,有内存泄漏的风险。
class A{};
class B:public A{};
A* pa = new B;//pa:指向子类对象的基类指针
delete pa;//实际仅执行基类的析构,可能内存泄漏.

解决方法:虚析构函数(后面讲)

7 子类的拷贝构造和拷贝赋值
1)子类的拷贝构造
–》如果子类没有定义拷贝构造函数,编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的拷贝。
–》如果子类缺省的拷贝构造无法满足当前需求(比如有浅拷贝的问题),那么在自定义拷贝构造函数时,需要使用初始化列表显式指明基类子对象也要以拷贝方式进行初始化。
class 基类{};
class 子类:public 基类{
//基类(that):指明基类子对象以拷贝方式初始化
子类(const 子类& that):基类(that),…{}
};

2)子类的拷贝赋值
–》如果子类没有定义拷贝赋值函数,编译器会为子类提供缺省的拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的复制
–》如果子类缺省的拷贝赋值函数无法满足当前需求(比如有浅拷贝的问题),那么在自定义拷贝赋值函数时,需要显式调用基类的拷贝赋值函数,完成基类子对象的复制。
class 基类{};
class 子类:public 基类{
子类& operator=(const 子类& that){

//显式调用基类的拷贝赋值函数
基类::operator=(that);
}
};

练习:复习继承语法
扩展练习V6.0:企业员工管理系统
需求:增加技术员和经理类
员工类(Employee)
/
技术员类 经理类

1)技术员:研发津贴(元/小时)
绩效工资:研发津贴*实际工作小时数*进度因数(输入)
实际工作小时数:8*23*出勤率2)经理:绩效奖金(元/月)
绩效工资:绩效奖金*绩效因数(输入)

09

回顾:
二十一 继承
1 概念
基类/子类
父类/派生类
2 语法
class 子类:继承方式 基类,…{…};
继承方式:public protected private
3 公有继承特性
1)可以把子类对象看做是基类对象,子类对象中包含基类的部分称为基类子对象。
2)向上造型:子类指针、引用–》基类指针或引用
3)向下造型:基类指针或引用–》子类指针、引用
4)子类继承基类的成员:公有、保护、私有
5)子类隐藏基类的成员,解决:“类名::”

4 继承方式和访问控制属性
1)访问控制属性:影响访问位置
2)继承方式:影响通过子类访问基类中成员的可访问性

5 子类的构造函数
1)没有指明基类子对象初始化方式,自动选择无参方式
2)可以使用初始化列表指明基类子对象初始化方式
3)创建子类对象
–》分配内存
–》构造基类子对象
–》构造成员子对象
–》执行子类构造函数代码

6 析构函数
1)子类对象销毁时,基类的析构也将自动被调用
2)子类对象销毁
–》执行子类析构函数代码
–》析构成员子对象
–》析构基类子对象
–》释放内存

7 子类的拷贝构造和拷贝赋值


今天:
二十一 继承(Inheritance)
8 多重继承
1)概念
一个子类可以同时基类多个基类,这样的继承方式被称为多重继承。

电话 播放器 计算机
\ | /
智能手机

2)多重继承在向上造型时,会根据各个基类子对象 在子类对象中的内存布局,自动进行偏移计算,保证指针的类型和其指向的目标对象类型一致。//参考mul_inherit.png

3)名字冲突问题
–》一个的子类的多个基类如果存在同名的成员,当通过子类访问时,编译器会报歧义错误,即名字冲突。
–》解决名字冲突的通用做法就是显式的加“类名::”,指明所访问的成员属于哪个基类//推荐
–》如果产生冲突的名字是成员函数,并且参数不同,也可以通过using声明,将它们引入到子类作用域,让它们在子类中形成重载,通过重接解析解//不推荐

9 钻石继承和虚继承
1)一个子类的多个基类源自共同的基类祖先,这样的继承关系被称为钻石继承
A
/
B C
\ /
D
A:可以称为公共基类
B、C:可以称为中间类
D:称为末端子类
2)钻石继承时,公共基类(A)子对象在末端子类(D)对象中会存在多份,通过末端子类访问公共基类的成员,会因为继承路径不同导致结果不一致。

3)通过虚继承,可以让公共基类子对象在末端子类对象中只有一份,通过末端子类访问公共基类的成员,即使路径不同所访问到公共基类的成员也一定是一致的。

4)虚继承语法
–》在继承表使用virtual关键字修饰
–》在继承链的末端子类负责构造公共基类子对象
A
/
B C//:virtual public A
\ /
D//末端子类负责构造公共基类子对象

在使用虚继承时,创建D对象,其继承结构会变成如下形式:
B C A
\ | /
D
虚继承原理(了解):参考diamond_virtual.cpp

二十二 多态(polymorphic)
eg:实现图形库,里面包含各种图形的类
图形基类(位置/绘制)
/
矩形(宽和高/绘制) 圆形(半径/绘制)
1 函数重写(虚函数覆盖)、多态的概念
1)如果将基类中的某个成员函数声明为虚函数,那么其子类中与它具有相同原型的成员函数就也是虚函数,并且对基类版本形成覆盖,即函数重写.
2)满足函数重写的要求后,再通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用虚函数,实际被执行的将是子类中的覆盖版本,而不再是基类中原始版本,这种语法现象被称为多态.
class Base{
public:
virtual void func(void){}//虚函数
};
class Derived:public Base{
void func(void){}//自动变成虚函数
};
Derived d;
Base* pb = &d;//pb指向子类对象的基类指针
Base& rb = d;//rb引用子类对象的基类引用
pb->func();//Derived::func
rb.func();//Derived::func

2 虚函数覆盖的条件(函数重写要求)
1)只有类中的成员函数才能被声明为虚函数,而全局函数、静态成员函数、构造函数不能为虚函数。
注:析构函数可以为虚函数(特殊,后面讲)
2)只有基类中以virtual关键字修饰的函数才能作为虚函数被子类覆盖,而与子类中的virtual无关.
3)虚函数在子类的覆盖版本和在基类中的原始版本必须具有相同的函数签名,即函数名、参数表、常属性一致。
4)如果基类中的虚函数返回基本类型的数据,那么子类中的覆盖版本必须返回相同类型的数据。
5)如果基类中的虚函数返回类类型的指针(A*)或引用(A&),那么允许子类的覆盖版本返回其子类类型的指针(B*)或引用(B&)。
class A{};
class B:public A{};

3 多态的条件
1)多态的语法特性除了要满足虚函数覆盖的条件,还必须通过指针或者通过引用调用虚函数,才能表现出来.
2)调用虚函数的指针也可以是成员函数中的this指针,当通过子类对象调用基类中成员函数,这时该成员函数中的this指针就是指向子类对象的基类指针,再通过它去调用虚函数同样可以表现多态的语法特性//重点掌握

eg:QT中多线程
class QThread{//QT定义好的,描述子线程类
public:
void start(){
run()
}
protected:
//线程入口函数
virtual void run(){

}
};
class MyThread:public QThread{
protected:
//线程入口函数
void run(){
//想要在子线程执行的代码
}
};
MyThread thread;
thread.start();

扩展练习V7.0:企业员工管理系统
需求:增加技术主管类
员工类(Employee)
/
技术员类 经理类
\ /
技术主管类
1)技术主管:研发津贴(元/小时) 绩效奖金(元/月)
绩效工资:(技术员绩效+经理绩效)/2

扩展练习V8.0:企业员工管理系统
需求:利用多态语法优化继承结构

10

回顾:
1 多重继承
1)一个子类同时继承多个基类
2)向上造型,自动进行偏移计算
3)名字冲突,解决–>“类名::”

2 钻石继承和虚继承
A(int m_data)
/
B C
\ /
D

解决:虚继承
A(int m_data)
/
B C//:virtual public A
\ /
D//负责构造公共基类子对象

3 多态
1)虚函数覆盖、多态概念
2)虚函数覆盖条件
–》成员函数才能被声明虚函数(析构函数)
–》virtual对基类版本加以声明
–》相同函数签名,函数名、常属性、参数一致
–》返回类型一般要求也是一致,类类型特殊
3)多态条件
–》必须通过指针或引用调用虚函数
–》this也可以

=================
今天:
二十二 多态

4 纯虚函数、抽象类和纯抽象类
1)纯虚函数
virtual 返回类型 函数名(形参表) = 0;
2)抽象类
如果类中包含了纯虚函数,那么该类就是抽象类
注:抽象类不允许创建对象
3)纯抽象类
如果类中所有成员函数都是纯虚函数,那么该类就是纯抽象类.

5 多态语法原理:多态语法通过虚函数表和动态绑定来实现的//了解
1)虚函数表会增加内存的开销
2)动态绑定的过程增加时间的开销
3)虚函数不能被内联优化
注:如果实际开发中没有多态的语法要求,最好不要使用虚函数

6 虚析构函数
1)基类的析构函数不能自动调用子类的析构函数,所以对一个指向子类对象的基类指针使用delete操作符,实际被调用的仅是基类的析构函数,子类的析构函数执行不到,有内存泄漏的风险。
2)可以将基类的析构函数声明为虚函数,那么这时子类的析构函数就也是虚函数,并且可以对基类的虚函数形成有效的覆盖,也可以表现多态的语法特性;这时再delete一个指向子对象的基类指针,实际被执行的将是子类的析构函数,子类的析构函数在执行结束后总会自动调用基类的析构函数,从而避免内存泄漏。

扩展练习V9.0:完成企业员工管理系统
需求:增加公司类,实现对员工对象增删改查操作
1)公司类使用单例模式创建,通过向量容器管理多个员工对象
2)增加:new员工对象添加容器中
3)删除:从容器中删除员工对象以及对应的文件
4)修改:根据ID修改工资
5)查询:根据ID查询员工信息,或者全部员工信息
扩展:向量容器使用(类似数组)
//Employee* empVector[…];
vector empVector;
//添加
empVector.push_back(new Employee(…));
empVector.push_back(new Manager(…));
//删除
empVector.erase(empVector.begin()+i);
//遍历
for(int i=0;i
empVector[i]->printInfo();
}

下午:16:30讲


ctags -R * //会生成tags文件
ctrl+] //跳转到光标所在函数的定义位置
ctrl+o //跳转回当前位置

11

回顾:
1 纯虚函数、抽象类和纯抽象类
2 多态原理:虚函数表和动态绑定
3 虚析构函数,避免内存泄漏

今天:
二十三 运行时类型信息//了解
1 typeid操作符
#include
typeid(类型/对象);//返回typeinfo对象
1)typeinfo类包含name成员函数,可以返回字符串形式类型信息
2)typeinfo类提供了“==”和“!=”操作符重载支持,可以直接类型之间的比较
注:如果类型之间具有多态的继承关系,那么typeid还可以利用多态语法确定实际目标对象的类型。

2 dynamic_cast动态类型转换操作符
语法:
目标变量=dynamic_cast<目标类型>(源类型变量)
适用场景:
用于具有多态特性父子类指针或引用的显式类型转换.
注:dynamic在类型转换期间,会检查目标对象和期望转换的类型是否一致,如果一致则转换成功,不一致则失败;如果转换的是指针返回NULL表示失败,如果转换的是引用则抛出异常"bad_cast"表示失败.

二十四 C++异常机制(Exception)
1 程序开发中的常见错误
1)语法错误
2)逻辑错误
3)功能错误
4)设计缺陷
5)需求不符
6)环境异常
7)操作不当

2 传统C语言的错误处理方式
1)通过返回值表示错误
优点:保证函数调用路径中的所有栈对象得到正确析构,不会出现内存泄漏。
缺点:错误处理流程比较复杂,需要逐层判断返回值,代码臃肿。
2)通过远程跳转处理错误
优点:错误处理流程简单,不需要逐层返回值判断,实现了一步到位的错误处理。
缺点:函数调用路径中的栈对象失去被析构的机会,会形成内存泄漏。

3 C++异常机制
结合两种传统错误处理的优点,同时避免它们的缺点,在形式上实现一步到位错误处理,同时还能保证所有的栈对象得到正确析构.

4 C++异常语法
1)异常抛出
throw 异常对象;
2)异常检测和捕获
try{
可能发生异常的代码;
}
catch(异常类型1){
针对异常类型1的处理
}
catch(异常类型2){
针对异常类型2的处理
}

注:catch子句根据异常对象类型自上而下顺序匹配,而不是最优匹配;因此对子类类型的异常捕获一定写在前面,否则将会被基类类型的异常捕获语句所提前截获。

5 函数的异常说明
1)用于说明函数可以抛出的异常类型
返回类型 函数名(形参表)throw(异常类型表){函数体}
2)函数异常说明是一种承诺,表示该函数所抛出的异常类型不会超出说明的范围。而如果抛出异常说明以外的类型,则无法被正常捕获,而会被系统检测到,导致进程终止.
3)函数异常说明两种极端形式
–》不写异常说明,表示可以抛出任何类型异常
–》空异常说明, “throw()”,表示不会抛出任何类型异常
4)如果函数的声明和定义分开写,要保证异常说明一致,但是顺序无所谓。

//补充虚函数覆盖条件
5)如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比基类版本抛出更多的异常,否则将会因为“放松throw限定”导致编译失败

6 标准异常类exception
class exception{
public:
exception() throw() { }
virtual ~exception() throw() ;

/*Returns a C-style character string describing the general cause of the current error.  */
virtual const char* what() const throw();

};

try{...
}
catch(exception& ex){ex.what();
}

7 构造函数和析构函数中的异常
1)构造函数可以抛出异常,但对象将被不完整创建,这样的对象其析构函数不再会被自动执行;因此在构造函数抛出异常之前,需要手动销毁之前分配的动态资源.
2)析构函数最好不要抛出异常

二十五 I/O流 //了解
1 主要I/O流类
ios
/
istream ostream
/ | \ / |
istrstream ifstream iostream ofstream ostrstream

2 格式化I/O
1)格式化函数(成员函数)
cout << 100/3.0 << endl;//33.3333
cout.precision(10);
cout << 100/3.0 << endl;//33.33333333

2)流控制符(全局函数)
cout << 100/3.0 << endl;//33.3333
cout << setprecision(10) << 100/3.0
<< endl;//33.33333333

3 字符串流
#include //过时
istrstream/ostrstream

#include //推荐
istringstream //类似sscanf
ostringstream //类似sprintf

4 文件流
#include
ifstream //类似fscanf
ofstream //类似fprintf

5 二进制I/O
//类似fread()
istream& istream::read(char* buffer,streamsize num)

//类似fwrite()
ostream& ostream::write(const char* buf,size_t num)


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部