文章目录
- 一、第一个C++程序
- 二、名字空间(命名空间) 改变作用域
- 三、C++的结构体,联合体,枚举
- 四、C++的字符串
- 五、C++的布尔类型: bool
- 六、操作符别名 //了解
- 七、C++的函数
- 八、C++的动态内存管理(什么是内存泄露,有什么危害)
- 九、C++的引用(Reference) (重点)
- 十、类型转换
- 十一、类和对象 //了解
- 十二、类的定义和实例化
- 十三 构造函数和初始化列表
- 十四 this指针和常成员函数
- 十五 析构函数(Destructor)
- 十六 拷贝构造和拷贝赋值
- 十七 静态成员(static)
- 十八 成员指针 //了解
- 十九 操作符重载
- 二十 继承(Inheritance)
- 二十一 多态(Polymorphic)
- 二十二 运行时类型信息 //了解
- 二十三 C++异常机制
- 二十四 I/O流 //了解
一、第一个C++程序
1 编译方式
1)gcc xx.cpp -lstdc++
tarena@ubuntu:~/me/C++/day01$ gcc first01.cpp -lstdc++
@ubuntu:~/me/C++/day01$ a.out
hello world!
2)g++ xx.cpp //推荐
tarena@ubuntu:~/me/C++/day01$ g++ first01.cpp
tarena@ubuntu:~/me/C++/day01$ a.out
hello world!
2 文件扩展名
1) .cpp //推荐
2) .cxx
3) .cc
4) .C
3 头文件
#include<iostream>
--> C++中和I/O相关的类型,对象,函数都在该头文件中
--> C++中绝大多数头文件没有.h后缀
--> C++开发中,可以直接使用c语言头文件中,同时还提供了一套不带.h的替换版本
eg:
#include <stdio.h> ==> #include<cstdio>
#include <stdlib.h> ==> #include<cstdlib>
#include <string.h> ==> #include<cstring>
4 标准的输入输出
1) 使用cin对象表示标准输入//类似scanf()
int i;
scanf("%d",&i);//C
cin >> i; //C++
-------------------------------------------------------
int i,double d;
scanf("%d%lf",&i,&d);//c
cin >> i >> d;//c++
注:">>"输入操作符
2) 用cout对象表示标准输出//类似printf()
int i=123;
printf("%d\n",i);//C
count << i << endl;//C++
注:"<<"输出操作符
--------------------------------------
int i = 123,double d = 4.56;
printf("%d,%lf\n",i,d);//C
cout << i <<','<< d << endl;//C++
二、名字空间(命名空间) 改变作用域
1 功能:
1) 避免名字冲突
2) 划分逻辑单元
2 定义名字空间
namespace 空间名{
名字空间成员1;
名字空间成员2;
...............
}
注:名字空间成员可以是全局函数,全局变量,自定义类型,名字空间.
3 名字空间成员使用
1)通过作用域限定操作符"::"
空间名::要访问成员;
eg:
"std::cout" 表示使用标准名字空间里面cout
2)名字空间指令
using namespace 空间名;
在该条指令以后的代码中,指定名字空间成员都可以见,可以直接访问,省略"空间名::"
3)名字空间声明
using 空间名::名字空间成员;
将名字空间中特定一个成员引入到声明所在的作用域中,在该作用域访问这个成员就如同是访问自己的成员一样,可以直接访问,省略"空间::"
4 全局作用域和无名(匿名)名字空间 //了解
1) 没有放在任何名字空间的成员,属于全局作用域,可以直接访问,如果和局部作用域的成员名字一样,局部优先;这时如果还希望访问到全局作用域的成员
可以通过"::成员"形式来显式指明
2) 定义名字空间时,可以没有名字,即为无名名字空间,对于无名空间里面的成员和全局作用域的成员一样,也可以直接访问,只是被局限在当前文件中
全局可在整个程序内使用
匿名名字空间只可以在本文件中使用
::变量名或函数名,是访问全局变量和全局函数的方法.
5 名字空间嵌套//了解
eg:
namespace ns1{
int num = 10;
namespace ns2{
int num = 20;
namespace ns3{
int num = 30;
}
}
}
cout << ns1::num << endl;
cout << ns1::ns2::num << endl;
cout << ns1::ns2::ns3::num << endl;
三、C++的结构体,联合体,枚举
1 结构体
1)当定义结构体变量时可以省略struct关键字
2)在结构体内部可以直接定义函数,称为成员函数(方法),成员函数中可以直接访问当前结构体中的其它成员
2 联合体(union)//了解
1)当定义联合体变量时可以省略union关键字
2)支持匿名联合
3 枚举
1)当定义枚举变量时,可以省略枚举关键字.
2)C++中枚举被看做是独立的数据类型,而c语言中枚举本质就是整型数.
enum STATE{SLEEP,RUN,STOP};
STATE s;
s = STOP;//C:ok,C++:ok
s = 2;//C:ok,C++:error
枚举可以当做返回类型.
四、C++的字符串
1 回顾c中字符串
1) 字面值常量 "hello"
2) 字符指针 char*
3) 字符数组 char[]
eg:
char* p1 = "abcdef";
char* p2 = "123456";
strcpy(p1,p2);//段错误
段错误信号 11
---------------------------
char arr1[] = "hello";
char arr2[] = "wangjianli";
strcpy(arr1,arr2);//内存越界,结果未知,危险!
--------------------------------
char arr[] = "hello";
char *p="world";
p = arr;//OK
arr = p;//error
---------------------------------------
2 C++可以完全兼容c中字符串表示方式,同时增加了string类型转换表示字符串.
- 1)字符串的定义
string s;//定义空字符串
string s = "xxxx"; //定义同时初始化
----------------------------------------------------
//下面两种写法实际初始化和上面相同
string s("xx");
string s = string("xx");
//char*类型指针,指向了一块在堆区动态分配的内存,这块内存存放了字符串的值.
namespace std{
struct string{
char *;
}
}
- 1) 字符串拷贝: =
string s1 = "hello";
string s2;
s2 = s1;//拷贝
- 2) 字符串的连接: + +=
string s1 ="abc";
string s2 = s1 + "def";
cout << s2 <<endl;//"abcdef"
- 3) 字符串比较: == != > < >= <=
if(s1 == s2){.....}
- 4) 随机访问: []
string s ="hello";
s[0] = 'H';
- 5) 成员函数
size()/length();//获取字符串长度
string str = "xxx";
str.size();
str.length();
c_str();//获取c风格的字符串(const char *)
string s1 ="xxx";
const char* s2 = "xxx";
s1 = s2;//OK
s2 = s1.c_str();//ok
void cfunc(const char* str){}
cfunc(str);
五、C++的布尔类型: bool
1 bool类型是C++中基本数据类型,专门表示逻辑值,逻辑值真为true,逻辑假为false
2 bool类型在内存占一个字节:1表示true,0表示false
3 bool类型变量可以接收任何类型表达式的结果,数值非零则为真,为零则为假.
六、操作符别名 //了解
&& <==> and
|| <==> or
^ <==> xor
{ <==> <%
} <==> %>
...
七、C++的函数
1 函数重载(overload)
1)定义
在相同作用域中,可定义同名函数,但是它们的形参必须有所区分(参数个数,参数类型,和形参变量名无关),这样的函数关系称为函数重载.
注:函数重载和返回类型无关
eg:图形库中绘图函数
void drawRect(int x,int y,int w,int h){....}
void drawCircle(int x,int y,int r){....}
......
--------------------------------------------------------
void draw(int x,int y,int w,int h){...}
void draw(int x,int y,int r){...}
......
2)函数重载匹配
调用函数时,编译器根据实参和形参匹配程度,自动选择最好的重载版本.
当前g++编译器匹配的优先级:(优先级以编译器的结果为准)
完全匹配>=常量转换>升级转换>降级转换>省略号匹配
char* > const char *
char = const char
- 当通过函数指针调用重载关系的函数时,根据指针类型选择匹配的重载版本.(函数指针在定义时已经匹配完成,函数指针是什么类型,调用什么类型的函数)
3)函数重载的原理 (函数的重入是什么????)
nm x.0
g++ 在编译函数,会进行函数换名
void func(int i,double j){}
_Z4funcid 4是函数名字符数 i 形参类型int d 形参类型double
C++的编译器是通过函数换名,将参数表的类型信息整合到新的函数名中,实现解决函数重载和名字冲突的矛盾.
(笔试题):C++中extern "C"声明作用?
可以在函数声明前面加入 extern "C" ,要求C++编译器不要对该函数进行换名,便于C程序直接调用.
注:被extern "C"声明的函数无法被重载.
--------------------------------------------
vi -O 用竖屏方式打开多个文件
vi -o 用横屏方式打开
vs
sp
Ctrl + W + W 进行换屏
2 函数的哑元参数 (C语言直接报错)
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);
}
3 函数的缺省参数(默认实参)
1) 可以为函数的参数指定缺省值,调用该函数时,如果不给实参,就会取缺省值作为默认实参.
void func(int a,int b=0/*缺省参数*/){...}
2) 靠右原则
如果函数的某个参数有缺省值,那么该参数右侧的所有参数都必须带有缺省值.
3) 如果函数声明和定义分开写,缺省参数应该写在函数声明部分,而定义部分不写(加注释标明);
void func(...);//函数声明
void func(...){...}//函数定义
- 4) 不要和函数重载形成歧义
void func(int a,int b=20,int c=30);
void func(int a);
func(10);
4 内联函数(inline) (笔试题)
1) 使用inline关键字修饰的函数即为内联函数,编译器将会 尝试 进行内联优化,可以避免函数的调用开销,提高代码的执行效率.
开销:保护现场,恢复现场......增加执行时间.
inline void func(void) {...}
内联关键字对程序的实现没有影响,影响的是编译器的执行.
内联是在编译时直接用函数机器指令替换调用语句的机器指令,避免调用的跳出跳入的时间耗费.(以空间换时间)
2) 使用说明
--> 多次调用的小而简单的函数适合内联函数
--> 调用次数极少或大而复杂的函数不适合内联
--> 递归函数不能内联(递归函数是否结束依赖于上一次的执行结果,执行次数不确定)
--> 虚函数不能内联(后面讲)
注: 内联优化只是一种建议,而不是强制要求,对于一个函数能否内联优化主要取决于编译器,有些函数不加inline修饰也会默认处理为内联优化;
有些函数即便加了inline修饰也会被编译器忽略掉.
八、C++的动态内存管理(什么是内存泄露,有什么危害)
1 回顾C语言中的动态内存管理
1)分配:
malloc()(calloc/realloc/brk/sbrk/mmap)
- 2)释放:
free()
2 C++的动态内存管理
1) 分配:
new,new[]
- 2) 释放:
delete,delete[]
内存分配时可以同时初始化 new int(200) 将存储区的值初始化为200
new[]分配,必须用delete[]释放,否则会发生内存泄露.
-std=c++11 使用C++11语法
笔试题(new,delete和malloc,free的区别)
进程: 栈段,堆段,数据段(全局区),代码段
九、C++的引用(Reference) (重点)
1 定义
1) 引用即别名,就是某个变量的别名,对别名操作和对变量本身操作完全相同.
2) 语法
类型 & 引用名 = 变量名;
注: 定义引用时必须初始化,初始化以后绑定的目标变量不能再修改.
注: 引用的类型和绑定的目标变量类型一致
eg:
int a = 100;
int &b = a;//b就是a的别名
2 常引用
1) 定义引用时加const修饰,即为常引用,不能通过常引用修改目标变量.(只读访问)
const 类型 & 引用名 = 变量名;
类型 const & 引用名 = 变量名; //等价
const int *p;
int const *p;//等价
int * const p;指针常量
int a=100;
const int &b=a;//b就是a的常引用
b++;//error
2) 普通的引用也可以称为左值引用,只能引用左值不能引用右值;而常引用也可以称为万能引用,既可以引用左值也可以引用右值.
注:关于左值和右值
左值(lvalue):可以放在赋值操作符的左侧,可以被修改,是左值
右值(rvalue):只能放在赋值操作符的右侧,不能被修改,是右值
3 引用型函数参数
1) 可以将引用用于函数参数,这是形参就是实参的别名,可以通过形参直接修改实参,
同时传递引用型参数可以避免参数值传递的过程,减小函数调用时间的开销,
提高代码执行效率,主要对自定义结构体类型参数效率提高效果显著.
2) 引用型参数有时可能会意外的修改实参值,
如果传递引用型参数仅是为了提高传参效率,不想修改实参值,
可以附加const修饰,将参数定义为常引用,提高传参效率的同时还可以接收常量型的实参。
4 引用型函数返回值
1)可以将 引用用于函数的返回值,这时函数返回结果就是return后面数据的别名,可以避免返回值的开销,提高代码的效率.
2)如果函数返回数值是左值引用,那么函数调用表达式结果也是左值.
注:不要从函数中返回自动局部变量的引用,因为所引用的目标变量内存会在函数返回以后被释放,使用危险!
可以从函数中返回成员变量,静态变量,全局变量的引用。
5.引用和指针有何区别联系(笔试题)
1)从c语言(汇编)的角度看待引用,其本质就是指针,但是C++开发中推荐使用引用而不是指针.
int i = 100;
int *pi = &i;
int &ri = i;
*pi <==> ri;
引用不占内存,指针占用内存空间.
2) 指针定义是可以不做初始化,指针的目标也可以修改(指针常量除外);而引用在定义时必须初始化,而且初始化以后其目标不能再修改.
int a = 3,b = 5;
int *p;//ok
p = &a;//p指向a的地址
p = &n;//p指向b的地址
----------------------------------
int &r;//error
int &r = a;
r = b;//不是修改目标,仅是赋值操作
//往下了解:
3) 可以定义指针的指针(二级指针),但是不能定义引用的指针;
int a = 10;
int *p = &a;
int **pp = &p;//ok,二级指针
--------------------------
int &r = a;
int &*pr = &r;//error,引用的指针
int *pr = &r;//ok,但指针是指向a的指针.
4) 可以定义指针的引用(指针变量的别名),但是不能定义引用的引用;
int a = 10;
int *p = &a;
int *&rp = p;//ok,指针的引用(指针的引用)
------------------------------------------------------
int &r = a;
int &&rr = r;//error,引用的引用 (C++11中叫右值引用)
int &rr = r; //ok,但就是普通引用,相当于还是给a取别名
5) 可以定义指针数组,但是不能定义引用数组,可以定义数组引用(数组的别名)
int a=10,b=20,c=30;
int *parr[3]={&a,&b,&c}; //ok,指针数组
int & rarr[3] = {a,b,c}; //error,引用数组
---------------------------------------------
int arr[3] = {a,b,c};
int (&rarr)[3] = arr;//ok,数组引用
6) 可以定义函数指针,也可以定义函数引用(函数的别名),语法特性一致
void func(int i,double d) {...}
int main(void){
void (*pfunc)(int,double) = func; //函数指针
void (*rfunc)(int,double) = func; //函数引用
pfunc(1,1.23);
rfunc(1,1.23);
return 0;
}
十、类型转换
1 隐式类型转换
char c = 'q';
int i = c; //隐式类型转换
-----------------------------------
void func(int i){}
func(c);//隐式
---------------------------------
int func(void){
return c;//隐式
}
2 显示类型转换
1) C++中兼容C中强制转换.(隐式转换降低可读性)
char c = 'q';
int i = (int)c;//C风格
int i = int(c);//C++风格,本质和上面一样
2)C++扩展的四种操作符形式的显示转换 编译器可以进行安全检查
-->静态类型转换
语法:
目标变量 = static_cast<目标类型>(源类型变量)
主要适用场景:
用于将void *转换为其他类型指针.
eg:
char c = 'q';
int i = static_cast<int>(c);
-->动态类型转换(后面讲)
语法:
目标变量 = dynamic_cast<目标类型>(源类型变量)
主要适用场景:
-->常类型转换
语法:
目标变量 = const_cast<目标类型>(源类型变量)
主要适用场景:
用于除去指针或引用的常属性.
const int ci = 10;
int *pci = const_cast<int*>(&ci);
编译优化导致 ci 和 *pci的值可能不同:
为了提高效率,ci中的在第一次使用,ci的值会放在CPU的寄存器中,每次读取直接从CPU的寄存器中取值,而不从内存取值
-->重解释类型转换
语法:
目标变量 = reinterpret_cast<目标类型>(源类型变量)
适用场景:
->在指针和整型数之间进行转换
->任意类型指针或引用之间进行转换
eg:向物理内存地址0x12345678,存放数据"123"
int *paddr=reinterpret_cast<int*>(0x12346578);
int *paddr=(int *)0x12345678
*paddr = 123;
小结: 来自C++社区给C程序员建议
1 慎用宏,可以使用const,enum,inline
#define PAI 3.14
--> const double PAI = 3.14;
#define SLEEP 0;
#define RUN 1;
#define 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*/ char[]
十一、类和对象 //了解
1 什么是对象
- 万物皆对象,任何一种事物都可以看做是对象.
2 如何描述对象
- 通过对象的属性(名词,数量词,形容词)和行为(动词)来描述和表达对象.
3 面向对象程序设计
- 对自然世界中对象的观察引入到编程实践的一种理念和方法,这种方法被称为"数据抽象",即描述对象时把细节的东西剥离出去,只考虑有规律性,一般性,统一性的东西.
4 类
类是将多个对象共性提取出来定义的一种新的数据类型,是对 对象属性和行为的抽象描述.
现实世界 类 虚拟对象
具体对象---抽象--->属性/行为---实例化-->具体对象
十二、类的定义和实例化
1 类的一般语法形式
struct/class 类名:继承方式 基类 {
访问控制限定符:
类名(参数表):初始化列表{...} //构造函数
~类名(void){...} //析构函数
返回类型 函数名(参数表){...} //成员函数
数据类型 变量;//成员变量
};
2 访问控制限定符
1)public: 公有成员, 任何位置都可以访问
2)private: 私有成员, 只有类中自己的成员函数可以访问
3)protected : 保护成员(后面讲)
注: struct 定义类,类中成员的缺省访问控制属性是public,而如果使用class定义类,类中成员的缺省访问控制属性是private.
eg:
struct/class XX{
int a; //公有成员(struct)/私有成员(class)
private:
int b; //私有成员
public:
int c; //公有成员
int d; //公有成员
private:
int e; //私有成员
};
3 构造函数(Constructor)
- 1) 语法
class 类名{
类名(形参表){
//主要负责初始化对象,即初始化成员变量
}
};
2) 函数名和类名相同,没有返回类型
3) 构造函数在创建对象时自动被调用,不能像普通函数显示的调用
4) 在每个对象的生命周期,构造函数一定会被调用,但仅被调用一次.
练习: 实现一个电子时间类,要求使用构造函数初始化时钟为当前的系统时间,并以秒为单位运行.
提示:
class Clock{
punlic:
构造函数(time_t )
private:
int 时,分.秒
};
Clock clock(time(NULL));
4 对象的创建和销毁
1) 在栈区创建单个对象
类名 对象名(构造实参); //直接初始化
类名 对象名=类名(构造实参); // 拷贝初始化,实际等价 类名(构造实参) 匿名对象,临时对象
类名 对象名 = 构造实参; // 仅适用于只有一个构造实参的时候使用,拷贝初始化的一种简化
string s("xx");
string s = string("xx");
string s = "xxx";
2) 在栈区创建多个对象(对象数组)
类名 对象数组[元素个数] = {类名(构造实参),...};
3) 在堆区创建单个对象
类名 *对象指针 = new 类名(构造实参);
int *pi = new int(200);
delete 对象指针; //销毁堆区创建的单个对象
4) 在堆区创建/销毁多个对象
类名 *对象指针 = new 类名[元素个数] {类名(构造实参),...};
int *parr = new int[10]{1,2,3,...};
delete[] 对象指针;
注:使用new操作符首先会分配内存,然后自动调用构造函数完成对象的初始化操作; 而如果使用malloc仅能实现内存分配,不会调用构造函数.
5 多文件编程
1) 类声明放在xx.h(头文件)中
2) 类的实现放在xx.cpp(源文件)中
十三 构造函数和初始化列表
1 构造函数可以重载,也可以带有缺省参数
string(); string s;
string( const string& s ); string s1 = string(s);
string( const char* str ); string s2 = string("hello");
2 缺省构造函数(无参构造函数)
1)如果类中没有定义任何构造函数,编译器会提供一个缺省的无参构造函数:
-->对于基本类的成员变量不做初始化
-->对于类 类型的成员变量(成员子对象),将会自动调用相应类的无参构造函数来初始化.
2)如果自己定义了构造函数,无论是否有参数,编译器都不会在提供缺省无参构造函数了.
3 类型转换构造函数(单参构造函数)
class 类名{
[explicit] 类名(源类型) {...}
};
可以实现将源类型变量转换为当前类类型对象.
注:可以使用explicit关键字修饰类型转换构造函数,可以强制这种转换必须要显示的完成.
4 拷贝构造函数(复制构造函数)
- 1) 用已存在的对象,作为同类型对象的构造实参,创建新的副本对象,将调用该类的拷贝构造函数.
class 类名{
类型(const 类名&){...}
};
class A{...};
A a1;
A a2(a1);// A a2 = a1; A a2 = A(a1); 拷贝构造
A a2;
a2 = a1; // 拷贝赋值
2) 如果类中没有定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数;
--> 对于基本类型成员变量,按字节复制
--> 对于类类型成员变量(成员子对象), 自动调用相应类的拷贝构造函数来初始化.
注:一般不需要自定义拷贝构造函数,因为缺省的拷贝构造函数已经很好用了.
3) 关于拷贝构造函数的调用时机
--> 用已定义对象作为同类型对象的构造实参
--> 以对象的形式向函数传递参数
--> 从函数中返回对象(有时会被编译器优化)
g++ cpCons08.cpp -fno-elide-constructors
去掉和构造函数相关的编译器优化
5 初始化列表
- 1) 语法
class 类名{
类名(参数表):成员变量(初值),成员变量(初值)...{
函数体;
}
};
int i;
i = 0;
-------------------
int i = 0;
eg:
//先定义成员变量,在赋初值
Student(const string &name,int age,int no){
cout << "构造函数" << endl;
m_name = name;
m_age = age;
m_no = no;
}
//定义成员变量同时初始化
Student(const string &name,int age,int no)
:m_name(name),m_age(age),m_no(no){}
2) 多数情况使用初始化列表和在构造函数体赋初值没有太大区别,两者可以任选;
但是有特殊场景,必须要使用初始化列表;
比如,类中包含了类类型的成员变量(成员子对象),而该类没有无参缺省构造函数,或希望以有参的方式来初始化该成员子对象,则必须使用初始化列表.
3)类中有"const"或"引用"成员变量,必须使用初始化列表.
十四 this指针和常成员函数
1 this指针
- 1) 类中的成员函数(包含构造函数,析构函数)都隐藏一个当前类类型的指针参数,名为this,在成员函数中访问类中的其他成员,本质都是this指针来实现的.
class A{
public:
int m_i;
//void print(A *this)
void print(void){
//cout << this->m_i << endl;
cout << m_i << endl;
}
};
int main(void){
A a(...);
a.print(); // a.print(&a);
return 0;
}
--> 对于普通成员函数,this指向调用对象的地址
--> 对于构造函数,this指向正在创建的对象地址
2)多数情况,使用this指针显示访问类中成员和直接访问,没有区别.
但是以下场景必须使用this指针:
--> 区分作用域
通过this可以区分成员变量和形参变量,通过this所访问的一定是成员变量.
--> 从成员函数返回调用对象自身 //重点掌握
--> 从类的内部销毁对象自身 delete this; // 了解
--> 作为成员函数的实参,实现对象之间的交互 //了解
2 常成员函数(常函数)
1) 在成员函数(类中普通的成员函数)参数表的后面加上const修饰,即为常成员函数.
返回类型 函数名(参数表) const {函数体}
2) 在常成员函数中this指针是一个常指针,不能在常成员函数中直接修改成员变量的值.
注: mutable关键字 mutable可变的;易变的
被mutable关键字修饰的成员变量,可以在常函数中直接修改.
3) 非const对象既可以调用常函数,也可以调用非常函数(普通函数); //重点掌握
但是常对象只能调用常函数,不能调用非常函数(普通函数).
注:常对象也包括常指针和常引用.
常函数里面不能调用非常函数!
4) 同一个类中,函数名和参数表相同的成员函数,其常版本和非常版本可以构成重载关系,常对象匹配常版本,非常对象匹配非常版本.
十五 析构函数(Destructor)
1 语法
class 类名{
~类名(void){
//主要负责清理对象生命周期中的动态资源
}
};
1)函数名必须是"~类名"
2)没有返回类型,也没有参数
3)析构函数不能被重载,一个类只能有一个析构函数
2 调用时机
当对象被销毁时,该类的析构函数将自动被执行
1)栈对象离开所在作用域时,其析构函数被作用域终止的右花括号"}"自动调用.
2)堆对象的析构函数被delete操作符自动调用.
注: delete对象时,首先会调用析构函数,清理对象所维护的动态资源,再销毁对象自身;而如果是free仅能销毁对象自身的内存,不会调用析构函数.
3 缺省析构函数
如果类中没有显示的定义析构函数,那么编译器会为该类提供一个缺省的析构函数:
--> 对于基本类型的成员变量什么也不做
--> 对于类 类型的成员变量(成员子对象),会自动调用相应类的析构函数.
4 对象的创建和销毁过程
1) 创建
--> 分配内存
--> 构建成员子对象(按声明顺序)
--> 执行构造函数代码
2) 销毁
--> 执行析构函数代码
--> 析构成员子对象(按声明逆序)
--> 释放内存
十六 拷贝构造和拷贝赋值
1 浅拷贝和深拷贝(面试题)
1) 如果类中包含了指针形式的成员变量,缺省的拷贝构造函数只是复制了指针变量本身,而不是复制了指针所指向的数据,这种拷贝方式被称为浅拷贝.
2) 浅拷贝将会导致 不同对象的数据共享,同时可能会在析构函数引发"double free"的异常,因此就必须自定义一个支持复制指针所指向数据的拷贝构造函数,即深拷贝.
浅拷贝:对于指针只是拷贝地址编号,数据共享,double free,内存泄漏(拷贝赋值)
深拷贝:拷贝指针所指向的内容,而不是拷贝地址编号
2 拷贝赋值
1) 当两个对象进行赋值操作时,比如"i2 = i3",编译器会自动将其翻译成i2.operator=(i3)成员函数调用形式,
其中"operator="被称为拷贝赋值操作符函数,由该函数实现两个对象的赋值运算,其返回结果就是表达式结果.
2) 如果自己没有定义拷贝赋值函数,那么编译器会为该类提供一个缺省的拷贝赋值函数,
但是缺省的拷贝赋值和拷贝构造类似,也是浅拷贝,有"double free",数据共享,内存泄露等问题,为了避免这些问题,需要自定义深拷贝赋值函数:
类名 &operator = (const 类名 &that){
if(&that != this){//防止自赋值
//释放旧内存
//分配新内存
//拷贝新数据
}
return *this;
};
- 注:this 指向左操作数,that 对应右操作数.
十七 静态成员(static)
1 静态成员变量
class 类名{
static 数据类型 变量名; //声明
};
数据类型 类名::变量名 = 初值;//定义和初始化
1) 普通成员变量属于对象,而静态成员变量不属于对象.
2) 普通成员变量需要在对象构造时定义和初始化,而静态成员变量需要在类的外部单独定义和初始化.
3) 静态成员变量和全局变量类似,被存放在数据段,可以把静态成员变量理解为被限制在类中使用的共享资源.
4) 使用
类名::静态成员变量;//推荐
对象.静态成员变量;//本质和上面等价
注:如果有const修饰的静态成员变量,可以在声明时初始化(特殊,了解)
2 静态成员函数
class 类名{
static 返回类型 函数名(参数表){...}
};
1) 静态成员函数没有this指针,也没有const属性.
2) 可以把静态成员函数理解为被限制在类中使用的全局函数.
3) 使用:
类名::静态成员函数(实参表); //推荐
对象.静态成员函数(实参表);//本质和上面等价
注: 在静态成员函数中只能访问静态成员,而非静态成员函数既可以访问静态成员也可以访问非静态的成员.
3 单例模式 书籍<设计模式> (重点)
1) 概念: 一个类只允许存在唯一的对象,并提供它的访问方试.
2) 实现思路:
--> 禁止在类的外部创建对象:私有化构造函数(缺省无参构造,拷贝构造).
--> 类的内部维护唯一的对象:静态成员变量
--> 提供单例对象的访问方法:静态成员函数
3) 创建方试:
--> 饿汉式: 单例对象无论用或不用,程序启动即创建.(以空间换时间)
使用场景:单例对象使用频率高,耗内存小
特点:以空间换时间
--> 懒汉式:单例对象用时再创建,不用即销毁.(以时间换空间)
使用场景:单例对象使用频率低,耗内存多
特点:以时间换空间
十八 成员指针 //了解
1 成员变量指针
1)定义
类型 类名::*成员指针变量名 = &类名::成员变量;
2)使用
对象.*成员指针变量名;
对象指针->*成员指针变量名;
注:".*"直接成员指针解引用操作符
"->*"间接成员指针解引用操作符
注:指针保存的是成员变量在对象中的相对地址.
2 成员函数指针
1)定义
返回类型 (类名::*成员函数指针)(参数表) = &类名::成员函数名;
2)使用
(对象.*成员函数指针)(实参表);
(对象指针->*成员函数指针)(实参表);
十九 操作符重载
基本概念
所谓的操作符重载就是一些具有特殊名字的函数,
"operator 连接需要重载某个操作符",比如"operator =",
"operator +"...,通过这样特殊的函数,把已定义的操作符重新定义,完成程序员想要的运算功能.
eg:复数(x + yi)
(1+2i)+(3+4i)=4+6i;
1 双目操作符重载 L#R
1.1 计算类双目操作符:+ - …
--> 左右操作数既可以是左值,也可以是右值;
--> 表达式结果是右值,禁止对表达式结果进行赋值.
--> 两种实现方式
1)成员函数形式(左调右参)
形如L#R表达式将会被编译器自动处理为L.operator#(R)的结果成员函数调用形式,由该函数完成运算功能,函数的返回结果就是表达式结果
L#R ==> L.operator#(R) const 类名 operator#(const 类名& 形参名)const{}
修饰返回类型,禁止表达式结果再赋值
常引用参数,支持常量型右操作数
常成员函数,支持常量型左操作数
2)全局函数形式(左右操作数都为参数)
形如L#R表达式将会被编译器自动处理为L.operator#(R)的结果成员函数调用形式,由该函数完成运算功能,函数的返回结果就是表达式结果
L#R ==> operator#(L,R) const 类名 operator# (const 类名 &l,const 类名 &r){}
注: 通过friend关键字,可以把一个全局函数声明为某个类的友元,友元函数可以访问类中的任何成员.
1.2 赋值类双目操作符:+= -= …
--> 左操作数必须是左值,右操作数可以左值也可以是右值
--> 表示式结果是左值,就是左操作数自身
--> 两种实现方式
1) 成员函数形式: L#R ==> L.operator#(R)
2) 全局函数形式: L#R ==> operator#(L,R)
2 单目操作符的重载 #O
2.1 计算类的单目操作符: ~ -(取负) ! …
--> 操作数可以是左值,也可以是右值
--> 表达式结果是右值,禁止对表达式结果再赋值
--> 两种实现方式:
1)成员函数形式: #O ==> O.operator#();
2)全局函数形式: #O ==> operator#(O);
2.2 自增减的单目操作符: ++,–
1)前++,--
--> 操作数一定是左值
--> 表达式结果也是左值,就是操作数自身
成员函数形式: #O ==> O.operator#();
全局函数形式: #O ==> operator#(O);
2)后++,--
--> 操作数一定是左值
--> 表达式结果是右值,是操作数自增减前副本.
成员函数形式: #O ==> O.operator#(int/*哑元*/);
全局函数形式: #O ==> operator#(O,int/*哑元*/);
3 输出(插入)和输入(提取)操作符重载:<< >>
功能: 为了实现自定义类型对象的直接输入或输出功能
注: 只能使用全局函数形式(friend)
#include <iostream>
ostream //标准库定义好的,表示标准输出流类
istream //标准库定义好的,表示标准输入流类
//全局函数: operator<<(cout,a)
ostream &operator<<(ostream &os,const RIGHT &r){
//....
return os;
}
cout << a << b << c <<...;
//全局函数: operator>>(cin,a)
istream &operator>>(istream &is,RIGHT &r){
//....
return is;
}
4 下表操作符:[]
功能:实现自定义类型的对象像数组一样使用
注: 常对象返回右值,非常对象返回左值.
string s = "hello";
s[0] = 'H';arr.operator[](0)
const string &cs = s;
cs[0] = 'H'; //error
5 函数操作符: ()
功能:实现自定义类型的对象像函数一样使用(仿函数).
注: 对参数个数,参数类型,返回左值类型没有任何限制.
class A{};
A a;
a(100,1.23);//a.operator()(100,1.23)
6 new/delete 操作符
static void* operator new(size_t size){
...
}
static void operator delete(void* pv){
...
}
7 操作符的重载限制
1)不是所有符号都能重载,下列操作符不允许重载
--> 作用域限定操作符"::"
--> 直接成员访问操作符"."
--> 直接成员指针解引用操作符".*"
--> 条件操作符"?:"
--> 字节长度操作符 "sizeof"
--> 类型信息操作符 "typeid" //后面讲
2) 如果重载的操作符,所有操作数都是基本类型,则无法被重载.
3) 操作符重载不会改变预定义的优先级和结合性.
4) 操作符重载无法改变操作个数(函数操作符可以).
5) 无法通过操作符重载机制发明新的符号.
6) 只能使用成员函数形式重载的操作符:
= () [] ->(后面讲)
二十 继承(Inheritance)
1 继承的概念//了解
通过一种机制表达类型之间共性和特性的方式,利用已有数据类型定义新的数据类型,这种机制就是继承.
eg:
人类:姓名,年龄,吃饭,睡觉
学生类:姓名,年龄,吃饭,睡觉,学号,学习
教师类:姓名,年龄,吃饭,睡觉,工资,讲课
...
--------------------------------------
人类:姓名,年龄,吃饭,睡觉
学生类继承人类:学号,学习
教师类继承人类:工资,讲课
...
人类(基类/父类)
/ \
学生 教师(子类/派生类)
基类--派生---> 子类
子类--继承---> 基类
2 继承语法
class 子类:继承方式 基类1,继承方式 基类2,...{
};
继承方式:
1)public:公有继承
2)protected:保护继承
3)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);
}
3) 向下造型(downset)
将基类类型的指针或引用转换为子类类型的指针或引用,
这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,可以显式转换(推荐静态转换static_cast)
4) 子类继承基类的成员
--> 在子类中,可以直接访问基类中的公有或保护成员,就好像它们声明在子类中一样.
--> 对于基类中的私有成员,子类也可以继承,但是会受到访问控制属性的限制,无法直接访问.但是可以让基类提供公有或保护提供的接口函数,来间接访问.
5)子类隐藏基类的成员
-->如果子类和基类中定义了同名的成员函数,因为作用于不同不能构成重载关系,而是隐藏关系.
-->如果需要通过子类访问基类中被隐藏的成员,可以借助"类名::"显示声明 //推荐
-->也可以通过using声明将基类中的成员函数引入到子类作用域中,让它们在子类中形成重载,通过参数重载解析解决.
4 访问控制属性和继承方式
1) 访问控制属性:影响访问该类成员的位置
| 访问控制 | 访问控制 | 内部 | 子类 | 外部 | 友元 |
| 限定符 | 属性 | 访问 | 访问 | 访问 | 访问 |
| public | 公有成员 | ok | ok | ok | ok |
| protected | 保护成员 | ok | ok | no | ok |
| private | 私有成员 | ok | no | no | ok |
2)继承方式:影响通过子类访问方式基类中成员的可访问性
| 基类中成员 | 公有继承子类 | 保护继承子类 | 私有继承子类 |
| 公有成员 | 公有成员 | 保护成员 | 私有成员 |
| 保护成员 | 保护成员 | 保护成员 | 私有成员 |
| 私有成员 | 私有成员 | 私有成员 | 私有成员 |
注:向上造型语法特性在保护继承和私有继承中不在使用.
5 子类的构造函数
1) 如果子类的构造函数没有指明基类子对象的初始化方式,将会自动选择基类的无参构造函数来初始化.
2) 如果希望基类子对象以有参的方式被初始化,则必须要使用初始化列表显示表示.
3)子类对象创建过程
--> 分配内存
--> 构造基类子对象(按继承表从左到右顺序)
--> 构造成员子对象(按声明自上而下顺序)
--> 执行子类构造函数代码
6 子类的析构函数
1) 子类的析构函数,无论是自定义还是缺省的,都会自动调用基类的析构函数,完成对基类子对象的销毁操作.
2) 关于子类的销毁过程
--> 执行子类析构函数代码
--> 析构成员子对象(按声明逆序)
--> 析构基类子对象(按继承表逆序)
--> 释放内存
3) 基类的析构函数不会自动调用子类析构函数,所以如果delete一个指向子类对像的基类指针,实际被执行的仅是基类的析构函数,
子类的析构函数执行不到,有内存泄露的风险!
class A{};
class B:public A{};
A *pa = new B;//pa:指向子类对象的基类指针
delete pa;//有内存泄露的风险
解决方法:虚析构函数(后面讲)
delete (B*)pa;//也可以解决问题,但容易遗漏,不推荐
7 子类的拷贝构造和拷贝赋值
1)子类的拷贝构造
--> 如果子类没有定义拷贝构造函数,那么编译器会为子类提供缺省的拷贝构造函数,它自动调用基类的拷贝构造函数,完成基类子对象的拷贝操作
--> 如果自定义了拷贝构造函数,编译器不在为子类提供缺省的拷贝构造,这是需要使用初始化列表,显示指明基类子对象也要以拷贝的方式进行初始化.
class Base{};
clase Derived:public Base{
//Base(that):指明基类子对象以拷贝方式初始化
Derived(const Derived &that):Base(that){}
};
2) 子类的拷贝赋值
--> 如果子类没有自己定义拷贝赋值函数,那么编译器会为子类提供一个缺省的拷贝构造赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的拷贝操作.
--> 如果子类自己定义了拷贝赋值函数,那么编译器不在为子类提供缺省的拷贝赋值函数,这时需要显示的调用基类的拷贝赋值函数,完成基类子对象的拷贝操作.
class Base{};
class Derived:public Base{
Derived &operator=(const Derived &that){
if(&that != this){
//显示调用基类的拷贝赋值函数,完成基类子对象的赋值操作
Base::operator=(that);
}
}
};
- 对象自恰性
8 多重继承
1) 概念
一个子类同时继承多个基类,这样的继承方式被称为多重继承
2) 向上造型时,编译器会根据各个基类子对象的内存布局,自动进行偏移计算,保证指针的类型和所指向的目标子对象类型一致.
3) 名字冲突问题
--> 多重继承,如果多个基类中存在相关的名字,通过子类访问时,编译器将会报歧义错误--名字冲突.
--> 解决名字冲突的通过做法是显示的使用"类名::",指明所访问的名字属于那个基类. // 推荐
--> 如果相同的名字是成员函数,并满足不同参重载条件,也可以使用using声明,让他们在子类中形成重载,通过参数的重载解析来解决.
9 钻石继承和虚继承
1) 一个子类的多个基类同时继承共同的基类祖先,这样继承结果称为钻石继承
A (int m_data)
/ \
B C //:virtual public A
\ /
D //负责构造公共基类子对象
注: A称为公共基类,B,C称为中间类,D称为末端子类
2) 创建末段子类D对象时,会包含多个公共基类(A)子对象,通过末端子类访问公共基类的成员,会因为继承路径不同而导致结果不一致.
解决方法 虚继承 虚基类表
3) 通过虚继承可以让公共基类子对象(A)在末端子类对象(D)中实现唯一,并且可以为所有的中间类(B,C)共享,这样即使沿着不同的继承路径,所访问到公共基类的成员也是一致的.
4) 虚继承语法:
--> 在继承表使用virtual关键字修饰
--> 位于继承链的末端子类负责构造公共基类子对象.
----------------------------------------------------------------------------------
eg:实现图形库,可以绘制各种图形
图形基类(位置/绘制)
矩形(宽和高/绘制) 圆形(半径/绘制)
二十一 多态(Polymorphic)
1 虚函数覆盖(函数重写),多态的概念
1) 如果将基类中某个成员声明为虚函数,那么子类中具有与其相同原型的成员函数就也变成虚函数,并且对基类子对象中的版本形成覆盖,即函数重写.
2) 满足了虚函数覆盖,再通过指向子类对象的基类指针或引用子类对象的基类引用,再调用虚函数,实际被执行的将是子类中的覆盖版本,
而不再是基类中原始版本,这种语法现象被称为多态.
class A{ virtual void func(){} };
class B:public A{ void func(){}};
B b;
A* pa = &b;//pa:指向子类对象的基类指针
A& ra = b;//ra:引用子类对象的基类引用
pa->func();//实际被执行的将是子类中的覆盖版本
ra.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(void){
run();
}
protected:
//线程入口函数
virtual void run(void){
...
}
};
class MyThread:public QThread{
protected:
//覆盖基类线程入口函数
void run(void){
想在子线程中执行的代码...
}
};
MyThread thread;
thread.start();
4 多态的实现原理 //了解
通过虚函数表和动态绑定实现的.
虚表指针 函数指针数组 函数指针 虚函数地址
*虚表指针 **虚表指针 ***虚表指针
1)虚函数表的生成会增加内存开销,在编译时生成
2)动态绑定的过程会增加时间效率,在运行时进行
3)虚函数不能被内联优化
注:如果没有多态的语法要求,最好不要使用虚函数
5 纯虚函数,抽象类和纯抽象类(面试笔试)
1) 纯虚函数
virtual 返回类型 函数名(形参表) = 0;
2) 抽象类
如果类中成员函数有纯虚函数,那么该类就是抽象类.
抽象--具体.
注:抽象类不能创建对象
3) 纯抽象类
如果类中所有的成员函数都是纯虚函数,那么该类就是纯抽象类.
工厂制造模式(好莱坞模式)
职责分离
6 虚析构函数
1)基类的析构函数不会自动调用子类析构函数,
所以如果delete一个指向子类对象的基类指针,
实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄露的风险!
//解决:虚析构函数
2)如果将基类的析构函数声明为虚函数(虚析构函数),这时子类的析构函数就也是虚函数,
并且可以对基类的虚析构函数形成覆盖,也可以体现多态的语法特性;
这是delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,
而子类的析构函数在执行结束后将会自动调用基类的析构函数,避免内存泄露.
二十二 运行时类型信息 //了解
1 typeid操作符
#include <typeinfo>
typeid(类型/对象),返回typeinfo对象用于描述类型信息
sizeof(类型/对象),size_t
注:typeinfo类中包含name成员函数,可以获得字符串形式的类型信息
注:typeinfo类中支持"== !="操作符,通过它们可以直接进行类型之间的比较,如果类型之间具有多态的继承关系,
typeid还可以利用多态语法确定实际的目标对象类型.
2 dynamic_cast操作符
语法:
目标变量 = dynamic_cast<目标类型>(源类型变量)
适用场景:
主要用于具有多态继承关系,指针或引用的显示转换.
注:在转换过程中,会检查目标变量类型和期望转换的类型是否一致.
如果一致则转换成功,如果不一致则失败,
如果转换指针返回NULL表示失败,如果转换是引用,抛出异常"bad_cast"表示失败.
二十三 C++异常机制
1 软件开发常见错误
1) 语法错误
2) 逻辑错误
gdb调试工具
3) 功能错误
4) 设计的缺陷
5) 需求不符
6) 环境异常
7) 操作不当
2 传统C的错误处理机制
1)通过返回值返回值错误
优点:函数调用路径中所有的栈对象都可以得到正确的析构函数,不会内存泄露
缺点:错误处理流程比较麻烦,需要对函数调用路径逐层进行返回值判断,代码臃肿
2)通过远程跳转处理错误
优点:不需要进行逐层返回值判断,实现一步到位错误处理,代码精炼.
缺点:函数调用路径中的栈对象失去被析构机会,有内存泄露的机会.
3 C++异常机制
1) 异常抛出
throw 异常对象;
注:异常对象可以基本类型,也可以是类类型
2) 异常检测和捕获
try{
//可能引发异常语句
}
catch(异常类型1){
针对异常类型1的处理
}
catch(异常类型2){
针对异常类型2的处理
}
...
注:catch子句根据异常对象类型自上而下顺序匹配,而非最优匹配.因此对子类类型异常捕获语句要写在前面,
不能写在对基类类型异常捕获语句后面,否则将被基类类型的异常捕获语句提前截获.
4 函数的异常说明
1) 用于说明函数可能抛出的异常类型
返回类型 函数名(形参表) throw(异常类型表) {函数体}
2) 函数的异常声明是一种承诺,表示该函数所抛出的异常不会超出说明的范围,
如果函数的实现者抛出了异常说明以外的类型,是无法被调用者正常捕获,而会被系统捕获,导致进程终止.
3) 两种极端形式
--> 不写异常说明,表示可以抛出任何异常
--> 空异常说明,"throw"表示不会抛出任何异常
4) 如果函数的声明和定义分开写时,要保证异常说明类型一致,但是顺序无所谓.
//补充虚函数覆盖条件:
5)如果基类中的虚函数带有异常说明,那么该函数在子类的覆盖版本不能说明比基类版本抛出更多的异常,否则将会因为"放松throw限定"导致编译失败.
5 标准异常类: exception
class exception
{
public:
exception() throw/*_GLIBCXX_USE_NOEXCEPT*/ { }
virtual ~exception() throw()/*_GLIBCXX_USE_NOEXCEPT*/;
/** Returns a C-style character string describing the general cause
* of the current error.*/
virtual const char* what() const throw() /*_GLIBCXX_USE_NOEXCEPT*/;
};
try{}
catch(exception &ex){
ew.what();
}
6 关于构造和析构函数的异常//了解
1) 构造函数可以抛出异常,但是对象将会被不完整创建,这样的对象的析构函数不再会自动被执行,
因此在构造函数抛出异常之前,需要手动释放之前分配的动态资源.
2) 析构函数最好不要抛出异常.
二十四 I/O流 //了解
1 主要的I/O流类
ios
/ \
istream ostream
/ | \ / | \
istrstream ifstream iostream ofstream ostrstream
scanf()/sscanf()/fscanf()
printf()/sprintf()/fprintf()
2 格式化I/O
- 1) 格式化函数(成员函数)
cout << 20/3.0 << endl;//6.66666 六个有效数字
cout.precision(10); 改变输出精度
cout << 20/3.0 << endl; // 6.66666666
- 2) 流控制符(全局函数)
cout << 20/3.0 << endl;//6.66666 六个有效数字
cout << setprecision(10) << 20/3.0 << endl;//6.666666666
endl(流控制符)
3 字符串I/O
#include <strstream> //过时,不要使用
istrstream/ostrstream
#inlcude <sstream>//推荐
istringstream //类似sscanf() "123"->123
ostringstream //类似sprintf() 123->"123"
4 文件I/O
#include <fstream>
ifstream //类似fscanf();
ofstream //类似fprintf();
5 二进制I/O
//类似fread()
istream &istream::read(char *buf,streamsize num);
//类似fwrite()
ostream &ostream::write(const char *buf,size_t num);
323

被折叠的 条评论
为什么被折叠?



