C++基础学习总结0001
来源:互联网 发布:淘宝药物货到付款 编辑:程序博客网 时间:2024/06/03 20:34
/*------------------第二章------------------------------------------------------------*/
<1>一些函数
#include <cmath>
double dRet = pow(x, y); //返回x的y次方 double pow(double x, double y)
int nRet = rand(void); //返回随机整数 int rand(void)
fabs(float) //获取浮点数绝对值
/*------------------第三章--------------*/
//--------整数
<1>类型
short ---:2个字节 //short int的简称
int ---:4字节
long ---:4字节 //long int的简称
long long ---:8字节 //long long int简称
char ---:1字节 //特殊的整数表示方式
bool ---:
unsigned int/short/long/long long /char //只能表示正数,正数范围扩大一倍
//如short范围是-32768-32768,而unsigned short是65535,其他类似
<2>sizeof-------返回类型或变量的字节数
sizeof(type) ----返回类型支持的字节数 如sizeof(int) = 4字节
sizeof(变量) ----int n[10];--sizeof(n) = 4*10;
1、Sizeof与Strlen的区别与联系
(1)sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
(2)sizeof是运算符,strlen是函数。
(3)sizeof可以用类型/函数做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
(4)数组做sizeof的参数不退化,传递给strlen就退化为指针了。
(5)strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
(6)大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
(7)sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
(8)当适用了于一个结构类型时或变量, sizeof 返回实际的大小,当适用一静态地空间数组, sizeof 归还全部数组的尺寸。sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸
<3>climits.h -----预定义了符号常量,用来表示各种类型限制,如INT_MAX表示int整形最大值
<4>类型 变量 = {} //这样赋值的变量将被初始化为0
<5>如何选择整形类型
int是最为自然的选择类型,除非有:变量不能存储为负数时则选择unsigned无符号类型、如果整数值大于16位则选择long,这样即使移植到16位系统也不至于int出错。
<6>如何确定常量的类型
一般常量会被确定为int类型,除非有:常亮有后缀,如l或L表示long,u或U表示unsigned int,ul/UL表示unsigned long,。。。。,根据长度判别
<7>const
定义常量:const type name = value; //如果定义时没有赋值,则常量值不确定且无法修改。
//只是定义一个不可变的变量,并没有存储于常量存储区;enum类型和#define宏,这两个都可以用来定义常量。
//const只修饰其后的变量,至于const放在类型前还是类型后并没有区别。如:const int a和int const a都是修饰a为const。注意*不是一种类型,如果*pType之前是某类型,那么pType是指向该类型的指针
//一个简单的判断方法:指针运算符*,是从右到左,那么如:char const * pContent,可以理解为char const (* pContent),即* pContent为const,而pContent则是可变的。
{
例:为什么下面的例子在使用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?
const int n = 5;
int a[n];
答案与分析:
1)这个问题讨论的是“常量”与“只读变量”的区别。常量,例如5, "abc",等,肯定是只读的,因为常量是被编译器放在内存中的只读区域,当然也就不能够去修改它。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时长度必须是“常量”,“只读变量”也是不可以的,“常量”不等于“不可变的变量”。但是在C++中,局部数组是可以使用变量作为其长度的。
}
-----const 和 #define区别
(1) 编译器处理方式不同
define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
(2) 类型和安全检查不同
define宏没有类型,不做任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(可以是堆中也可以是栈中)。
(4)const 可以节省空间,避免不必要的内存分配。 例如:
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。
(5) 提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,
没有了存储与读内存的操作,使得它的效率也很高。
----const相比define优点:
(1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。
(2)有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
【规则5-2-1】在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
5.3 常量定义规则
【规则5-3-1】
需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的
常量集中存放在一个公共的头文件中。
【规则5-3-2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。
例如:
const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
----类中的常量(类中const常量未被初始化时编译会报:必须在构造函数基/成员初始值设定项列表中初始化)
有时我们希望某些常量只在类中有效。由于#define定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用const修饰数据成员来实现。const数据成员的确是存在的,但其含义却不是我们所期望的。const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。
const数据成员的初始化只能在类构造函数的初始化表中进行。
*怎样才能建立在整个类中都恒定的常量呢?别指望const数据成员了,应该用类中的枚举常量来实现。例如
class A
{…
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
};
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如PI=3.14159)。sizeof(A) = 1200;其中枚举最长空间。
enum EM { SIZE1 = 100, SIZE2 = 200}; // 枚举常量 sizeof(EM) = 4;
//--------浮点数
<1>书写法则
标准小数点表示法,如:m.n
E表示法,必须是浮点数,可以使小写e,指数可以是负数或正数;
如:m.nEx = m.n*10的x次方,Ex表示E的x次方
<2>类型
float ---:32位
double ---:64位
long double ---:80/90/128位
<3>cout.setf()
使得C++输出时对于浮点数最后一位0,不舍弃;
<4>浮点常量
一般情况下浮点常量为 double
若想要 float 常量,则在后缀加上f/F
若想要 long double 常量,则在后缀加上l/L
/*------------------------------------第四章:复合类型---------------------------------*/
<1>数组
初始化规则:
(1)只能在定义时进行全部一次初始化,以后只能通过数组下标初始化;
如:type name[size] = {...}; //C++ 可以用:type name[size] {...};
(2)如果初始化未写全,那么剩下的元素值将会初始化为0;
(3)初始化不能缩窄转换,即不能: long lName = {1.0, 2.0}; //浮点转long变窄,所以会报错
<1.1>数组的替代品
(1)vector: ---堆
使用法则:#include <vector> using namespace std; vector<typename> vt(n);
//typename数据类型,(n为定义元素个数(可为变量)) 如:vector<int> vt(5) 包含五个元素的int类型数组
1、尾部插入数字:vt.push_back(a);
2、使用下标访问元素,vt[0];记住下标是从0开始的。
3、使用迭代器访问元素. vt.begin()/vt.end();
vector<int>::iterator it;
for(it=vt.begin();it!=vt.end();it++)
cout<<*it<<endl;
(6)插入元素: vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;
(7)删除元素: vec.erase(vec.begin()+2);删除第3个元素
vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始
(8)向量大小:vec.size();
(9)清空:vec.clear();
(10)vector的元素不仅仅可以使int,double,string,还可以是结构体,但是要注意:结构体要定义为全局的,
否则会出错。
(2)array: ---栈
使用法则:#include <array> using namespace std; array<typename, n>; //n必须常量
可以将array数组赋给另一个arrar数组,但普通数组不可
<2>字符串
(1)C风格初始化字符串必须以'\0'空字符结束,如:char str[10] = {'a', 'b'...., '\0'} //若最后没有'\0',就不是字符串,是数组
(2)如果用""双引号初始化的话就不必添加'\0',系统会自动添加;
<3>结构简介 --- struct
(1)struct name{...};
(1)typedef struct name{...}别名;
<4>共用体 ---union
(1)union name{.....}; 一次只能存储一种类型,最大空间为存储类型里的最大类型的最大空间值
<5>枚举 ---enum
(1)也可以创建符号常量
<6>指针
(1)指针加1,其增加的值等同于他指向的变量类型所占用的字节数;
(2)数组名即数组第一个元素所指向的地址,sizeof后为数组总长度;
//--------------数据存储位置
一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
《1》自动存储: ---存储于栈中
在函数内部定义的常规变量使用自动存储(自动变量/局部变量),在函数调用时自动产生,
函数调用结束自动消除;
《2》静态存储:
整个程序执行期间都存在;
定义方式:(1)在函数外定义 (2)定义前加 static
《3》动态存储: ---存于堆中
使用new、delete....定义等变量;
//----------------static
作用:
static静态变量声明符。在声明它的程序块,子程序块或函数内部有效,值保持,在整个程序期间分配存储器空间,编译器默认值0。
是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性。
(1)静态全局变量有以下特点:
该变量在全局数据区分配内存;
未经初始化的静态全局变量会被程序自动初始化为0(在函数体内声明的自动变量的值是随机的,除非它被显式初始化,而在函数体外被声明的自动变量也会被初始化为0);
静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;
静态变量都在全局数据区分配内存,包括静态局部变量。
但定义静态全局变量还有以下好处:
静态全局变量不能被其它文件所用;
其它文件中可以定义相同名字的变量,不会发生冲突;
全局变量和全局静态变量的区别:
1)全局变量是不显式用static修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern 全局变量名的声明,就可以使用全局变量。
2)全局静态变量是显式用static修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用extern声明也不能使用。
(2)静态局部变量:在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
该变量在全局数据区分配内存;
静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
(3)静态函数:
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
定义静态函数的好处:
静态函数不能被其它文件所用;
其它文件中可以定义相同名字的函数,不会发生冲突;
(4)面向对象(类中的static关键字)
静态数据成员:在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。
静态数据成员有以下特点:
对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。
静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:数据类型 类名::静态数据成员名 = 值;
类的静态数据成员有两种访问形式:类对象名.静态数据成员名 / 类类型名::静态数据成员名
静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。
同全局变量相比,使用静态数据成员有两个优势:
静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
(5)静态成员函数:它为类的全部服务而不是为某一个类的具体对象服务。
静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this 是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指 针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。
关于静态成员函数,可以总结为以下几点:
出现在类体外的函数定义不能指定关键字static;
静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
非静态成员函数可以任意地访问静态成员函数和静态数据成员;
静态成员函数不能访问非静态成员函数和非静态数据成员;
由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
调用静态成员函数,可以用成员访问操作符(.)和(->;)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:类名::静态成员函数名(参数表)
//----为什么要引入static
函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。
//----什么时候用static
需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。
//----内部机制
静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。
静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态
数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
//----应用格式
引用静态数据成员时,采用如下格式:
类名::静态成员名
//----注意事项
⑴类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
⑵不能将静态成员函数定义为虚函数。
⑵初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
⑵初始化时不加该成员的访问权限控制符private,public等;
⑵初始化时使用作用域运算符来标明它所属类;
⑵数据类型 类名::静态数据成员名 = 值; //初始化
⑼为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。在各通信公司的笔试面试中经常出现的考题就是static的作用及功能。
//*-----C语言中
static 函数内部函数和外部函数:当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
内部函数:
(又称静态函数)如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static 函数类型 函数名(函数参数表){……}
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
外部函数:
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern] 函数类型 函数名(函数参数表){……}
调用外部函数时,需要对其进行说明:[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
//----------------extern
extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。另外,extern也可用来进行链接指定。
extern int a; //声明一个全局变量a
int a; //定义一个全局变量a
extern int a =0 ; //定义一个全局变量a 并给初值。一旦给予赋值,一定是定义,定义才会分配存储空间。
(1)声明外部变量:
现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相不透明的。也就是说,在编译时,全局变量的可见域限制在文件内部。
{
创建一个工程,里面含有A.cpp和B.cpp两个简单的C++源文件:
//A.cpp
int i;
int main()
{
}
//B.cpp
int i;
这两个文件极为简单,在A.cpp中我们定义了一个全局变量i,在B中我们也定义了一个全局变量i。
我们对A和B分别编译,都可以正常通过编译,但是进行链接的时候,却出现了错误,错误提示如下:
Linking...
B.obj:error LNK2005:"inti"(?i@@3HA)already defined in A.obj
Debug/A.exe:fatal error LNK1169:one or more multiply defined symbols found
Error executing link.exe.
A.exe-2 error(s),0 warning(s)
这就是说,在编译阶段,各个文件中定义的全局变量相互是不透明的,编译A时觉察不到B中也定义了i,同样,编译B时觉察不到A中也定义了i。
但是到了链接阶段,要将各个文件的内容“合为一体”,因此,如果某些文件中定义的全局变量名相同的话,在这个时候就会出现错误,也就是上面提示的重复定义的错误。
}
因此,各个文件中定义的全局变量名不可相同。
在链接阶段,各个文件的内容(实际是编译产生的obj文件)是被合并到一起的,因而,定义于某文件内的全局变量,在链接完成后,它的可见范围被扩大到了整个程序。
文件中定义的全局变量的可见性扩展到整个程序是在链接完成之后,而在编译阶段,他们的可见性仍局限于各自的文件。
extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
(2)声明外部函数:
extern函数1:如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别:extern int f(); 和int f();
extern函数2:
/*------------------------------------第五章,循环与关系表达式---------------------------------*/
略
/*------------------------------------第六章,逻辑---------------------------------*/
<1>文件操作
(1)写入文件:
必须包含:#include <fstream>、#include <iostream>
ofstream outFile; outFile.open(FileName); outFile << "内容"(与out类似); outFile.close();
(2)读取文件:
必须包含:#include <fstream>、#include <iostream>
ifstream inFile; inFile.open(FileName); inFile >> value(与in类似);
inFile.good(); //最后一次读取操作是否成功
inFile.eof();(判断结束)
inFile.close();
C语言风格:http://www.cnblogs.com/duzouzhe/archive/2009/10/24/1589348.html
FILE * f = fopen("FileName", "Mode"); //Mode
if (f)
{
SYSTEMTIME t;
GetLocalTime(&t); //获取系统时间
fprintf(f, "[%02d,%02d:%02d:%02d.%03d] %s: ", t.wDay, t.wHour, t.wMinute, t.wSecond, t.wMilliseconds, lpszMsg); //写入
fprintf(f, "(Process is %d, thread is %d)\r\n\t[", GetCurrentProcessId(), GetCurrentThreadId());
va_list params;
va_start(params, lpszFmt);
int len = vfprintf(f, lpszFmt, params);
va_end(params);
fprintf(f, "]\r\n");
fclose(f);
}
/*------------------------------------第七章,C++编程模块---------------------------------*/
;
/*------------------------------------第八章,C++函数探幽---------------------------------*/
<1>内联函数
(1)函数声明与定义前加inline; 如:inline double Pam(param..){....};//一般写在一行
(2)与define不同的是inline是按值传递参数,而define只是文本替换;
(3)与非inline函数不同的是,inline函数必须在调用该函数的每个文本文件中定义。为保证不会发生这样的事情,建议把inline函数的定义放到头文件中。
(4)关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。所以说,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
(5)定义在类声明之中的成员函数将自动地成为内联函数
(6)内联能提高函数的执行效率,但内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
<2>引用变量
(1)声明引用变量(必须在声明时初始化):int rats;--->int & rodents = rats; //int & 指向int的引用,将引用变量作为参数时,函数使用的是原始数据,因此改变他等同于改变原数据,类似于指针,若不希望被修改,则定义const 引用
(2)引用变量与原数据地址/值完全相同,一变全变;
(3)引用结构:struct Name {};---->Name &Fun(Name &ft){...}; //Fun函数参数为结构体引用,返回值也是引用;效率更高
<3>默认参数
(1)必须从右到左添加默认值;
<4>函数重载
(1)当函数基本上执行相同任务,但使用不同形式数据时使用。
<5>函数模版
(1)template <typename Anytype> --Fun(Anytype xx); //其中关键字template、typename是不可少的,除非将typename换为class;Anytype可以任意选(一般为简单写为T);
//使用时可用:Fun<具体类型>(param)具体形式,也可Fun(param)隐式根据参数类型推断调用。
(2)并不创建任何函数,只是告诉编译器如何定义函数。他不能缩短可执行程序,编译时编译器会按照模板创建相应类型函数,编译后程序中没有模板;
(3)若函数中使用同一种算法,但是类型不同时,则使用模板;(typename与class都可用)
(4)类模板是一个类家族的抽象,它只是对类的描述,编译程序不为类模板(包括成员函数定义)创建程序代码,但是通过对类模板的实例化可以生成一个具体的类以及该具体类的对象。
(5)与函数模板不同的是:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定;
如:
类继承时template <class T> class Fclass{里面使用到T} --->class Cclass:public Fclass<放入实例化对象(类型可以是此类)>{...};
template <class/typename T> class className{T...}; --->className<实例化> test;
实例:
template <class T>
class classTemplate{
public:
classTemplate(){;};
int getValue()
{
return T::value();
}
};
class classTemplateC:public classTemplate<classTemplateC>//继承时实例化
{
public:
classTemplateC(){;};
static int value(){return 10;};
int getValueC()
{
return getValue();
}
};
<1>重载的模板
(1)与函数重载类似template <typename T> --Fun(T xx); template <typename T>--Fun(T xx, T yy, Type zz); //可出现其他类型Type
<6>显示具体化---基于模板
(1)对于给定函数名,可以有:常规函数、模板函数、显示具体化函数以及它们各自的重载版本;(常规函数优先于显示具体化函数优先于模板函数)
(2)template <> Fun<type>(type param...); //type为具体化类型,也可写为template <> Fun(type..)
//实例
template <typename T> //函数模板
T test(T a, T b)
{
return a+b;
}
template <> int test<int>(int a, int b) //具体化函数
{
return a += b;
}
double test(double a, double b) //普通函数
{
return a+b;
}
//调用时优先根据不同类型,优先匹配普通函数,类型未匹配则匹配具体化函数,最后匹配模板函数
<7>显示实例化
(1)template Fun<type>(type param...); //type为实例化类型
<8>类模板
(1)显示实例化: template class ArrayTp<string,10>;
显示具体化: template <class T, int n>/template <> class ArrayTp<string,10>
从中可以看出,二者的区别类似于函数模板中二者的区别,一个以template 打头,一个以template <> 打头,但都要有class 关键字,因为这是类模板。
/*------------------------------------第九章,内存模型与名称空间---------------------------------*/
<1>单独编译
(1)头文件一般包含:函数原型、使用#define/const定义的常量、结构、类的声明、模板声明、内联函数等
引用外部变量extern,引用时不初始化变量否则会重新声明变量并分配新的空间
(1)volatile:告诉编译器不进行优化,volatile char *vptr;
//提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读
//取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,
//将出现不一致的现象。
(2)mutable:指出即使结构或类变量为const形式,其某个成员也可以改变 struct date{param...; mutable type typename;}; 其中typename值可变
(3)const:const定义的全局变量链接性为内部(就像使用static一样)
函数与链接性:默认为外部链接的,加static后为内部链接
/*------------------------------------第十章,对象与类---------------------------------*/
<一>注意点
(1)不必在声明中说明private,因为类成员默认为private(与结构唯一区别是结构成员默认是public的)
(2)类方法可以访问类的private成员,友元也可以访问private成员
(3)定义在类声明中的成员函数自动变为inline函数(他的规则是在每个使用的文件中都得定义,因此一般放于头文件中)
(4)构造函数的参数表示的不是类成员,而是赋给类成员的值;他是用了创建对象的,调用与对象生成之前,因此不能通过对象来调用;
<二>this指针
(1)this指针用来指向调用成员函数的对象;因此*this就是该对象。
<三>类作用域
(1)定义成员函数时必须用作用域形式:"类名::函数()"
(2)类中定义常量
枚举enum:在类中属于整个类,不属于某个对象,因此可以当常量使用(在类对象未出现前使用,const常量不可以)
static const:
<四>抽象数据类型
(1)
/*------------------------------------第十一章、使用类---------------------------------*/
<一>运算符重载-----
(1)C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符。
(2)重载之后运算符的优先级和结合性都不会改变。
(3)运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来说,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
(4)不能重载的运算符只有五个,它们是:成员运算符“.”、指针运算符“*”、作用域运算符“::”、“sizeof”、条件运算符“?:”。
(5)运算符重载形式有两种:重载为类的成员函数和重载为类的友元函数。
*当运算符重载为类的成员函数时,函数的参数个数比原来的操作个数要少一个;当重载为类的友元函数时,参数个数与原操作数个数相同原因是重载为类的成员函数时,如果某个对象使用重载了的成员
*函数,自身的数据可以直接访问,就不需要再放在参数表中进行传递,少了的操作数就是该对象本身。而重载为友元函数时,友元函数对某个对象的数据进行操作,就必须通过该对象的名称来进行,因
*此使用到的参数都要进行传递,操作数的个数就不会有变化。
//方式
函数类型 operator 运算符(形参表){;;;} //运算符重载为类的成员函数,函数类型就是运算结果类型;ClassName operator+(ClassName &test); -->ClassName ClassName::operator+(const ClassName &test){...};
friend 函数类型 operator 运算符(形参表){;;;}//(用于第一个操作数不是本类对象,如<</>>重载时第一个操作数为ostream/istream类型)运算符重载为类的友元函数,声明中有关键字friend,定义中没有,且定义中不属于类,不能用“::”
(6)运算符重载后,优先级和结合性:
用户重载新定义运算符,不改变原运算符的优先级和结合性。这就是说,对运算符重载不改变运算符的优先级和结合性,并且运算符重载后,也不改变运算符的语法结构,即单目运算符只能重载为单目运
算符,双目运算符只能重载双目运算符。
(7)编译程序如何选用哪一个运算符函数:
运算符重载实际是一个函数,所以运算符的重载实际上是函数的重载。编译程序对运算符重载的选择,遵循着函数重载的选择原则。当遇到不很明显的运算时,编译程序将去寻找参数相匹配的运算符函数。
(8)重载运算符有哪些限制:
(9) 不可臆造新的运算符。必须把重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中。
(10) 重载运算符坚持4个“不能改变”。
·不能改变运算符操作数的个数;
·不能改变运算符原有的优先级;
·不能改变运算符原有的结合性;
·不能改变运算符原有的语法结构。
<二>友元
(1)指某些虽然不是类的成员却能够访问类的所有成员的函数
(2)为了使其他类的成员函数来访问该类的私有变量;不是类的成员函数,调用时不通过对象
(3)什么时候使用:
可以用于运算符重载
两个类共享数据
(4)存在位置:可以在类内声明,类外定义,可放在类的私有段或公有段,放在私有段和公有段无区别。
<一+二>实例
(1)运算符重载--成员函数
class C_Test12{ //.h
private:
int len;
...
public:
C_Test12(int nLen) {len = nLen;};
//成员函数
C_Test12 operator+(const C_Test12 &test12_01);
C_Test12 operator-(const C_Test12 &test12_01);
C_Test12 operator*(const C_Test12 &test12_01);
C_Test12 operator/(const C_Test12 &test12_01);
C_Test12 operator-();
char operator[](int index);
//友元
friend C_Test12 operator+(const C_Test12 &test12_01, const C_Test12 &test12_02);
friend C_Test12 operator-(const C_Test12 &test12_01, const C_Test12 &test12_02);
friend C_Test12 operator*(const C_Test12 &test12_01, const C_Test12 &test12_02);
friend C_Test12 operator/(const C_Test12 &test12_01, const C_Test12 &test12_02);
friend std::ostream & operator<<(std::ostream &os, const C_Test12 &st);
friend std::istream & operator>>(std::istream &is, const C_Test12 &st);
}
//.cpp
C_Test12 C_Test12::operator+(const C_Test12 &test12_01)
{
return C_Test12(len + test12_01.len);
}
C_Test12 C_Test12::operator-(const C_Test12 &test12_01)
{
return C_Test12(len - test12_01.len);
}
C_Test12 C_Test12::operator*(const C_Test12 &test12_01)
{
return C_Test12(len * test12_01.len);
}
C_Test12 C_Test12::operator/(const C_Test12 &test12_01)
{
return C_Test12(len / test12_01.len);
}
C_Test12 C_Test12::operator-()
{
len = -len;
return *this;
}
char C_Test12::operator[](int index)
{
if (index >= 0 && index < 10)
{
return szOperator[index];
}
}
//运算符重载--友元
C_Test12 operator+(const C_Test12 &test12_01, const C_Test12 &test12_02)
{
return test12_01.len + test12_02.len;
}
C_Test12 operator-(const C_Test12 &test12_01, const C_Test12 &test12_02)
{
return test12_01.len + test12_02.len;
}
C_Test12 operator*(const C_Test12 &test12_01, const C_Test12 &test12_02)
{
return test12_01.len + test12_02.len;
}
C_Test12 operator/(const C_Test12 &test12_01, const C_Test12 &test12_02)
{
return test12_01.len + test12_02.len;
}
std::ostream & operator<<(std::ostream &os, const C_Test12 &st)
{
os<<st.len;
return os;
}
std::istream & operator>>(std::istream &is, const C_Test12 &st)
{
is>>st.len;
return is;
}
//test
C_Test12 test12_02(10), test12_01(20), test12_03;
test12_03 = test12_02 + test12_01;
test12_03 = test12_02 - test12_01;
test12_03 = test12_02 * test12_01;
test12_03 = test12_02 / test12_01;
test12_03 = -test12_02;
char szOperator = test12_02[3];
std::cout<<test12_02;
std::cin>>test12_02;
<三>转换函数
(1)要将类对象转换为其他类型,必须定义转换函数;指出如何进行这种转换。转换函数必须是成员函数。
(2)转换函数没有返回类型、没有参数、但必须返回转换后的值。
---:operator typename(); //typeName 指出了要转换成的类型.因此不需要返回值.转换函数是类方法意味着,它需要通过类对象来调用,从而告知函数要转换的值,因此,函数不需要参数.
(3)C++可以利用构造函数将一个int型变量转换为一个类对象,如CMyClass aClass(5);构造函数只能用于
某种类型到类类型的转换.要进行相反的转换,则需要使用重载操作符函数--转换函数来实现.
(4)注意一下三点:
1 转换函数必须是类方法.2 转换函数不能返回指定类型. 3 转换函数不能有参数.
(5)实例---必须是类方法
//转换函数
operator int(); //.h
//转换函数.cpp
C_Test12::operator int()
{
return len;
}
//test
C_Test12 test12_02(10);
std::cout<<test12_02;
<四>通过将构造函数声明为explicit(显式)的方式可以抑制隐式转换。也就是说,explicit构造函数必须显式调用。
如:ClassName构造函数定义为explicit ClassName(int n);时
ClassName classTest(N);//显示转换可以
CLassName classTest = N;//隐身转换不行
/*------------------------------------第十二章、类和动态内存分配---------------------------------*/
<一>动态内存和类
(1)C++默认提供如下函数:
1、默认构造函数:带参数的构造函数也可以是默认构造函数(只要所有参数都有默认值),只有构造函数才可以使用成员初始化列表形式初始化类成员;
对类成员进行初始化,需对类成员指针进行初始化。
2、默认析构函数:析构函数无参数,所以也没有重载情况
3、复制构造函数:复制构造函数的形参为本类对象的引用类型。//或叫拷贝构造函数,浅拷贝(遇指针类成员函数时容易出错)
将一个对象复制到新创建的对象中---ClassName(const ClassName &)
当新建一个对象,并用同类型对象初始化时使用。
ClassName test01;-->ClassName test02(test01);//或者ClassName test02 = ClassName(test01);...
显示复制构造函数:当类中含有指针型的数据成员、需要使用动态内存的, 最好手动显式定义复制构造函数来避免一些问题。 //深拷贝
ClassName(const ClassName &obj);--->m_pszCpy为指针,需在析构释放
ClassName::ClassName(const ClassName &obj)
{
m_pszCpy = new char[10];
strcpy(m_pszCpy, obj.m_pszCpy);
}
4、赋值运算符:将已有对象赋值给另一对象,使用运算符重载"="。 //浅赋值,遇到指针类成员函数时需显示写深赋值的赋值函数
5、取地址运算符:className name1(param..);-->claaName name2 = &name1;//这里的&就会用到取地址运算符函数
className* ClassName::operator&()
{
return this;
}
6、取常量地址运算符:claaName name1;-->const claaName name2 = &name1;//这里的&就会用到取常量地址运算符函数
const className* ClassName::operator&() const
{
return this;
}
(2)不能在类声明中初始化静态成员变量,因为声明只描述了如何分配内存,但并不分配内存;对于静态成员,可以在类声明之外单独初始化(静态成员是单独存储的,单独初始化不需要加static关键字)。
<二>静态类成员函数
(1)如果声明和定义是分开的,则声明必须要static,定义不需要。
(2)不能通过对象调用static成员;也不可使用this指针;如果是公有的,则可以使用--ClassName::staticFun();调用
(3)只能使用静态成员。
/*------------------------------------第十三章、类继承---------------------------------*/
<一>类继承----公有继承、保护继承、私有继承
(1)使用公有继承,基类的公有成员将成为派生类的公有成员;基类的私有成员也成为派生类一部分,但只能通过基类的公有和保护方法访问。
(2)创建派生类对象前首先创建基类对象,派生类构造函数需以成员初始化列表形式调用基类构造函数(不写会默认调用默认构造函数)。
(3)可以将基类指针指向派生类;但不能将派生类指针指向基类。
<二>is-a关系---公有继承
(1)派生类对象也就是一个基类对象,可以对基类以及派生类进行任何操作。
<三>多态公有继承----方法覆盖、虚函数
(虚函数表_vfptr:存储类的虚函数地址(类里面无论有几个虚函数,虚函数表指针只有一个),在构造父类时会将其所有虚函数地址放于虚函数表中;当派生类中重写了这个虚函数时会将派生类的函数地址覆盖(覆盖)掉这个基类
中的虚函数,因此实现父类可以通过这个地址访问派生类的函数(偷梁换柱来实现访问派生类,所以派生类重写基类虚函数时名称、参数等都必须相同))
(1)如果没有virtual关键字,程序将根据引用或指针类型来选择方法;如果使用virtual,程序将根据引用或指针指向的对象来选择方法。
(2)方法在基类中被声明为virtual时,在派生类中自动变为virtual;在基类中声明虚析构函数是
为了按正确的顺序释放对象。如果析构函数不是virtual的,则只会调用对应指针类型的析构函数。
虚函数必须实现,如果不实现,编译器将报错。构造函数,友元不可为虚函数。
(3)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏.
(4)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)(要访问被隐藏的基类方法,需用:对象.基类::方法 形式)。
(5)派生类不必加关键字virtual(声明与定义分开始定义中也不必加virtual)
<四>静态联编、动态联编----虚函数--(多态)
联编是指一个计算机程序的不同部分彼此关联的过程
(1)静态联编:在编译过程中进行的连编。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。
(2)动态联编:程序运行中进行。实际上是在运行时虚函数的实现。动态联编的优点是灵活性强,但效率低。(如虚函数只有在运行时才可根据指向对象类型来决定调用函数)
动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)
实现动态联编需要同时满足以下三个条件:
1、必须把动态联编的行为定义为类的虚函数。
2、类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来。
3、必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。
4、动态联编要求派生类中的虚函数与基类中对应的虚函数具有相同的名称、相同的参数个数和相同的对应参数类型、返回值或者相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中虚函数所返回的指针或引用的基类型的子类型。如果不满足这些条件,派生类中的虚函数将丢失其虚特性,在调用时进行静态联编。
<五>抽象基类-----(纯虚函数)----目的是为所有派生类提供公共的接口
*抽象类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象类不能被实例化,通常是作为基类供子类继承,子类中重写虚函数(不需要关键字等),实现具体的接口。
(1)作用:
在很多情况下,基类本身生成对象是不合情理的。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数
可以将接口与实现分离。
引入抽象基类和纯虚函数方便实现C++的多态特性。可以用抽象基类的指针去调用子类对象的方法。
(方法:virtual ReturnType Function()= 0;),声明结尾必须为"=0"则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。
(2)抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
<六>访问控制
(1)protected:与private,在类外只能通过公有成员来访问。派生类可以访问基类protected成员。
(2)private:派生类,或者类外都只能通过公有成员访问。与(1)唯一区别就是派生类的访问上。
(3)public:
/*-----------------------------------第十四章、代码重用---------------------------------*/
<一>has-a
1、包含对象成员的类---包含:提供了两个被显示命名的对象成员
类的成员对象必须在当前类的成员初始化列表中初始化(除非原类有无参构造函数);
(1)valarray类简介:
?valarray类是由头文件valarray支持的,valarray被定义为一个模板类,以便能够处理不同的数据类型.
2、私有继承---组合:提供了两个无名的子对象成员
?私有继承提供的特性与包含相同:获得实现,但不获得接口.所以,私有继承也可以用来实现has-a关系.
(1)初始化基类组件:
1、对于继承类,新版本的构造函数将使用成员初始化列表语法,它使用类名而不是成员名来标识构造函数.
(2)访问基类的方法:
1、使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法.
(3)访问基类对象:
1、使用作用域解析运算符可以访问基类的方法,但如果要使用基类对象本身,该如何做呢?答案是使用强制类型转换.
将基类对象强转换,即(对象类型)*this
(4)访问基类的友元函数:
1、用类名显式的限定函数名不适合于友元函数,这是因为友元不属于类.然而,可以通过显示地转换为基类来调用正确的函数.
(基类对象引用)当前对象;
ostream & operator << (ostream &os, concat ClassName &cs){ os << (基类对象引用)cs<<":\n";}
2、在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针.
3、使用包含还是私有继承?大多数C++程序员倾向于使用包含.
(1)首先,它易于理解.
(2)其次,继承会引起很多问题,尤其从多个基类继承时.
(3)另外,包含能够包括多个同类的子对象.
?然而,私有继承所提供的特性确实比包含多
?另一种需要使用私有继承的情况是需要重新定义虚函数.派生类可以重新定义虚函数,但包含类不能.
?提示:通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承.
4、保护继承:
?保护继承是私有继承的变体.保护继承在列出基类时使用关键字protected.
(1)使用保护继承,基类的公有、保护成员都成为派生类的保护成员;和私有继承一样,基类的接口在派生类中是可用的,但在继承层次外不可用。
5、使用using重新定义访问权限
(1)?注意,using声明只使用成员名—没有圆括号,函数特征标和返回类型.
(2)?using声明只适用于继承,而不适用于包含.
using 基类::方法;//只需给出方法名,无括号等
<二>多重继承
1、虚基类-----钻石继承
(1)虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象.
(2)C++对这种新特性也使用关键字virtual—有点像关键字重载.(public与virtual次序随意)
class A{..}; class B: virtual public A{..}; class C: public virtual A{..};--->class D: public B, public C{...};
2、新的构造函数规则
(1)C++在基类是虚的时,禁止信息通过中间类自动传递给基类.--->如上,禁止D类通过B或C类将传输传给A的构造函数。
(2)如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数.如上,需在D构造函数中用D(param):A(param);显示调A来初始化基类A。
(3)警告:如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式的调用该虚基类的某个构造函数.
3、哪个方法?
?对于单继承,如果没有重新定义Show(),则将使用最近祖先中的定义.而在多重继承中,每个直接祖先都有一个Show()函数,这使得上述调用是二义性的.
?警告:多重继承可能导致函数调用的二义性.
?总之,在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则.另外,如果在编写这些类时没有考虑到MI,则还可能需要重新编写它们.
4、混合使用虚基类和非虚基类
当类通过多条虚途径和非虚途径继承某个特定的基类时,该类包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象.
5、虚基类和支配
一个成员名如何优先于另一个成员名呢?派生类中的名称优先于直接或间接祖先类中的相同名称.
虚二义性规则与访问规则无关.
<三>类模板
类被定义为模板时,其成员函数全部变为模板函数,每个函数(包括构造函数等默认函数)定义前都得加模板头:template<typename T>(除非声明与定义在一起);使用模板类时必须全部以className<类型>整体形式使用。
当实例化一个模板时,编译器必须看到模板确切的定义,而不仅仅是它的声明;因此类模板声明和定义得在同一文件中。
1、定义类模板
(1)模板类以下面这样的代码开头:template <class/typename Type....>//模板参数中可出现其他具体类型
(2)模板成员函数:--每个(不管是否使用Type)都需以template <class/typename Type>开头,且需ClassName<Type>::限定
template <class/typename Type, int n> Type ClassName<Type, n>::Fun(Type...){...};
2、使用模板类
?仅在程序包含模板并不能生成模板类,而必须请求实例化.
?注意:必须显式地提供所需的类型,这与常规的函数模板是不同的,因为编译器可以根据函数的参数来信来确定要生成哪种函数.
claaName<具体类型> classTest();//与常规函数不同,实例化类模板对象时必须显示给定类型。
3、深入探讨模板类
?可以使用指针栈,但如果不对程序做重大修改,将无法很好地工作.编译器可以创建类,但使用效果如何就因人而异了.
正确使用指针栈:
?使用指针栈的方法之一是,让调用程序提供一个指针数据,其中每个指针都指向不同的字符串.注意,创建不同指针是调用程序的职责,而不是栈的职责.栈的任务是管理指针,而不是创建指针.
4、数组模板示例和非类型参数
?注意模板头template < class T,int n > ,关键字class(或在这种上下文中等价的关键字typename)指出T为类型参数,int指出n的类型为int.这种参数(制定特殊的类型而不是用作泛型名)称为非类型non-type或表达式expression参数.
?表达式参数有一些限制.表达式参数可以是整型,枚举,引用或指针.
?模板代码不能修改参数的值,也不能使用参数的地址.
5、模板多功能性
?可以将用于常规类的技术用于模板类.模板类可用作基类,也可用做组件类,还可用作其他模板的类型参数.
template <typename T>
class className1
{....}
template <typeName Tp>
class className2
{
className1<Tp> cls;
}
//使用
claaName1 <className2<int> > test;
(1)递归使用模板:className< className<int, 5> 10> test;
(2)使用多个类型参数 :template <class T1, class T2>....
(3)默认类型模板参数 :template <class T1, class T2 = int>....
(4)
6、模板的具体化
(1)隐式实例化:className <具体类型> test;
(2)显式实例化:template class className <具体类型> test;
当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化.声明必须位于模板定义所在的名称空间中.
(3)显式具体化:template <>class className <具体类型,如:int , char..>{.....};//如果具体化所有类型,则template后面的<>内为空,否则如部分具体化则不为空
其是特定类型(用于替换模板众的泛型)的定义.有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同.在这种情况下,可以创建显式具体化.
(4)部分具体化:template <typename T1, typename T2>class className <T1, int>{.....}; //T2被具体化为int,T1不变
C++还允许部分具体化,即部分限制模板的通用性.
7、成员模板
(1)模板可用作结构,类或模板类的成员.要完全实现STL的设计,必须使用这项特性.
(2)将模板用作参数:template <template <typename, T> class clsaaName> class className1{className<..> test;//使用模板参数}//template <typename, T> class clsaaName为模板
(3)模板类和友元:
??模板类声明也可以有友元.模板的友元分3类:
1、非模板友元:友元函数定义在模板体内。
template <typename T> class classNameB{..friend void test(){....};..friend void test(className<T> &){....};}
2、约束bound模板友元,即友元中模板的类型取决于类被实例化时的类型(友元与类中模板类型一致):
?要约束模板友元作准备,要使类的每一个具体化都获得与友元匹配的具体化.
显示地在重载的运算符或者函数后面加上模板声明< T >,告诉编译器友元函数是一个类型一致的模板。
如果希望使用函数与模板特化的类型相对应,则使用他。
template <typename T> class classNameB{..friend void test<T>(){};..friend void test<T>(className<T> &){};}
//必须在友元中加<模板类型>,如test<T>中的<T>l;声明与定义分开始声明中必须加<T>,定义中得加模板头template <typename T>
3、非约束unbound模板友元,即友元的所有具体化都是类的每一个具体化的友元(友元模板与类模板不同,分开定义).
模板体内要另建模板,如果希望使用函数与模板特化的类型相独立,则使用他。
?通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元.
?对于非约束友元,友元模板类型参数与模板类类型参数是不同的.
template <typename T> class classNameB{ template <typename C,typename D>friend void test(C &, D &){};}//声明定义分开写时声明定义前都得加模板形式template <typename C,typename D>
8、模板别名(C++11)
(1)可以使用typedef为模板具体化制定别名.C++11新增了一项功能—使用模板提供一系列别名.
typedef className<具体化> defName; -->defName test;
(2)C++11允许将语法using=用于非模板.用于非模板时,这种语法与常规typedef等价.
<四>总结
/*------------------------------------第十五章,友元、异常和其他---------------------------------*/
<一>友元
?也可以将类作为友元,在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员.
1、友元类------friend class className;//在类中声明友元类
?友元声明可以位于公有,私有或保护部分,其所在的位置无关紧要.
class Tv
{
private:
int channel;
int mode;
protected:
int vol;
public:
void set_mode();
friend class Remote;
};
class Remote
{
private:
int mode;
public:
void set_mode(Tv &t){ t.set_mode(); } //#1
void set_vol(int c, Tv&t){t.vol= c;}// #2 访问 类的保护成员
void set_chan(int c, Tv&t){t.channel = c;} // #3 访问 类的私有成员
};
2、友元成员函数---friend type className::Fun1(); //className为
class className{type Fun(..){..};}-->class classNameA{friend type className::Fun(..){...};}//className类的Fun声明为classNameA的友元函数
?必须小心排列各种声明和定义的顺序
1.前向声明的使用。
2.类定义置后。(保证当一个类的定义中 使用另一个类的方法时,所用的方法在此定义之前,已声明)。
class Tv; //Note 1:前向声明,因为Note2处用到Tv &t,需告诉编译器,此处Tv为类
class Remote
{
....
void set_chan(Tv &t, int c);
}
class Tv
{
...
friend void Remote::set_chan(Tv &t,int c);// Note:3 声明 友元成员函数
}
3、共同的友元
?需要使用友元的另一种情况是,函数需要访问两个类的私有数据.
?声明的在每个类中声明,定义值定义一份
<二>嵌套类
?对类进行嵌套与包含并不同.包含意味着将类作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效.
?对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突.
1、嵌套类和访问权限
(1)作用域:
如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它.
如果嵌套类是在另一个类的保护部分声明的,则它对于后者来说是可见的,但是对于外部世界则是不可见的.然而,在这种情况中,派生类将指导嵌套类,并可以直接创建这种类型的对象.
如果嵌套类是在另一个类的公有部分声明的,则允许后者,后者的派生类以及外部世界使用它,因为它是公有的.然而,由于嵌套类的作用域为包含它的类,因此在外部世界使用它时,必须使用类限定符.
class claaName{..class classNameA{..};.}-->外部访问claaName::claaNameA test;//限定符
(2)访问控制:对嵌套类访问权的控制规则与对常规类相同.
总之,嵌套类声明的位置决定了类的作用域或可见性。
(3)模板中的嵌套:
<三>异常-------try/cathe/throw
try
{//捕获语句}
catch(type1){//捕获type1类型的异常}
catch(type2){//捕获type2类型的异常}
....
catch(...){//捕获各种类型的异常}
1、调用abort------cstdlib(stdlib.h)中
2、返回错误码
?一种比异常终止更灵活的方法是,使用函数的返回值来指出问题.
?传统的C语言数学库使用的就是这种方法,它使用的全局变量名为errno.当然,必须确保其他函数没有将该全局变量用于其他目的.
3、异常机制
(1)异常提供了将控制权从程序的一个部分传递到另一部分的途径.
(2)对异常的处理有3个组成部分:
?引发异常:
?使用处理程序捕获异常:
?使用try块:
(3)
?程序使用异常处理程序exception handler来捕获异常,异常处理程序位于要处理问题的程序中.catch关键字表示捕获异常.
?执行throw语句类似于执行返回语句,因为它也将终止函数的执行;但throw不是将控制权返回给调用程序,而是导致程序沿函数调用序列后退,直到找到包含try块的函数.执行完try块中的语句后,如果没有引发任何异常,则程序跳过try块后面的catch块,直接执行处理程序后面的第一条语句.
?如果函数引发了异常,而没有try块或没有匹配的处理程序时,将会发生什么情况.在默认情况下,程序最终将调用abort()函数,但可以修改这种行为.
try{Fun();}catch(char *s){....};---Fun(){....throw "err......";};
(4)将对象用作异常类型
通常引发异常的函数将传递一个对象,这样可以使用不同的异常类型来区分不同的函数在不同的情况下引发的异常;另外,对象可以携带消息,程序员可以根据这些消息来确认引发的异常原因。同时catch可以根据这些消息来处理。
(5)异常规范和C++11
?异常规范,这是C++98新增的一项功能,但C++11却将其摒弃了.这意味着C++11仍然处于标准之中,但以后可能会从标准中剔除,因此不建议您使用它.
4、栈解退
(1)假设try块没有直接调用引发异常的函数,而是调用了对引发异常的函数进行调用的函数,则程序流程将从引发异常的函数调到包含try块和处理程序的函数.C++通常通过将信息放在栈中来处理函数调用.
(2)引发机制的一个非常重要的特性是,和函数返回一样,对于栈中的自动类对象,类的析构函数将被调用.
(3)异常极其重要的一点:程序进行栈解退以回到能够捕获异常的地方时,将释放栈中的自动存储型变量.如果变量是类对象,将为该对象调用析构函数.
5、其他异常特性
(1)
6、exception类
//////////////////////////////////////////////////////////////////////////////////////////////////////
//自定义异常类
template <typename Type>
class classErr
{
public:
classErr(const char *strErr, int val):str(strErr), value(val){;}
~classErr(){;}
void _What()const
{
cout <<str<<endl;
cout <<value<< "not push!"<<endl;
}
private:
int value;
string str;
};
template <typename T>
class className
{
public:
className(int n)
{
nToo = 0;
nNum = n;
list = new T[nNum];
}
bool isFull()
{
if(nToo == nNum)
return true;
else
return false;
}
bool insert(T p)
{
if(isFull())
{
throw classErr<T>("栈已满,不能入栈!", p);//直接赋值异常类,抛出异常
}
list[nToo] = p;
nToo++;
return true;
}
bool isEmpty()
{
if(nToo == 0)
return true;
else
return false;
}
T push()
{
nToo--;
if(isEmpty())
{
throw classErr<T>("栈空,不能出栈!", 0);//直接赋值异常类,抛出异常
}
list[nToo] = 0;
return list[nToo];
}
virtual ~className()
{
if(NULL != list)
{
delete list;
}
}
private:
int nNum;
int nToo;
T *list;
};
int _tmain(int argc, _TCHAR* argv[])
{
className<int> Test(10);
try
{
for(int i = 0; i< 10; i++)
{
Test.insert(i);
}
}
catch(const classErr<int> &e) //捕获异常
{
e._What();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
(1)stdexcept异常类
异常类系列logic_error描述了典型的逻辑错误.
异常invalid_argument指出给函数传递了一个意料外的值.
异常length_error用于指出没有足够的空间来执行所需的操作.
异常out_of_bounds通常用于指示索引错误.
runtime_error异常系列描述了可能在运行期间发生但难以预计和防范的错误.
(2)bad_alloc异常和new
对于使用new导致的内存分配问题,C++的最新处理方式是让new引发bad_alloc异常.
(3)空指针和new
C++标准提供了一种在失败时返回空指针的new,其用法如下:
7、异常,类和继承
(1)异常,类和继承以三种方式相互关联.首先,可以像标准C++库所做的那样,从一个异常类派生出另一个;其次,可以在类定义中嵌套异常类声明来组合异常;第三,这种嵌套声明本身可被继承,还可用作基类.
8、异常何时会迷失方向
(1)异常被引发后,在两种情况下,会导致问题.
首先,如果它是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配(在继承层次结构总,类类型与这个类及其派生类的对象匹配),否则称为意外异常.
(2)如果异常不是在函数中引发的(或者函数没有异常规范),则必须捕获它.如果没被捕获(在没有try块或没有匹配的catch块时,将出现这种情况,则异常被称为未捕获异常.
<四>RTTI
1、RTTI是运行阶段类型识别(Runtime Type Identification)的简称.RTTI旨在为程序在运行阶段确定对象的类型提供一种标准方式.
<五>类型转换运算符
1、类型转换运算符
class Base{..}-->class A : public Base{...};
dynamic_cast:将基类类型的指针或引用转换为同一继承层次中其他类型的引用或指针;dynamic_cast操作符其实执行两个动作,首先检查转换是否有效,如果无效则进行错误处理,如果有效,进行转换
Base *bs = new A; A *a = dynamic_case<A*>(bs);//一般将基类转为派生类指针用(一般情况不允许基类指针或引用转为派生类指针或引用)
A a; Base &bs = &a; A &a1 = dynamic_case<A &>(bs);//引用转换形式
const_cast: 常量转化为非常量,const B *b1 = new B();-->B *b2 = const_cast<B*>(b1);
(1)常量指针被转化成非常量指针,并且仍然指向原来的对象;
(2)常量引用被转换成非常量引用,并且仍然指向原来的对象;
(3)常量对象被转换成非常量对象。
static_cast:用法:static_cast < type-id > ( exdivssion ) 该运算符把exdivssion转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
(1)用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
(1)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
(1)把空指针转换成目标类型的空指针。
(1)把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉exdivssion的const、volitale、或者__unaligned属性。
reinterpret_cast:reinterpret_cast (exdivssion)它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针
(1)int n=9; -->double d=reinterpret_cast (n);
<六>总结
?友元使得能够为类开发更灵活的接口
?嵌套类是在其他类中声明的类.
?C++异常机制为处理拙劣的编程事件,如不适当的值,I/O失败等,提供了一种灵活的方式.
?RTTI(运行阶段类型信息)特性让程序能够检测对象的类型.
/*------------------------------------第十六章、string类与标准模板库---------------------------------*/
<一>string类----------头文件string
1、构造函数:
(1)string(const char *s)
(2)string(size_type n, char ch):创建一个包含n个元素的字符串,每个初始化为ch
(3)string(const string &str)
(4)string():创建默认string对象,长度为0
(5)string(const string &s, size_type n):创建用s前n个字节初始化的string对象
(6)template <class Iter> \n string(Iter begin, Iter end)//
(7)string(const string &s, size_type pos, size_type n)//初始化为s中从pos开始的n个字符
(8)string(string && str) noexcept:
(8)string(initializer_list<char> il)
2、........
<二>智能指针模板类-----头文件memory
1、auto_ptr:属于STL,当然在 namespace std 中,包含头文件 #include<memory> 便可以使用。std::auto_ptr 能够方便的管理单个堆内存对象。
使用 std::auto_ptr 时,绝对不能使用“operator=”操作符。//浅拷贝导致多次析构会引发异常
//class className{....}-----std::auto_ptr<className> cn (new className(param));//className为类型,可为各种类型,
//因为他的构造函数中有explicit,所以只能显示初始化
2、uniuqe_ptr://class className{....}-----std::uniuqe_ptr<className> cn (new className(param));//className为类型,可为各种类型,
//因为他的构造函数中有explicit,所以只能显示初始化
3、shared_ptr://class className{....}-----std::shared_ptr<className> cn (new className(param));//className为类型,可为各种类型,
//因为他的构造函数中有explicit,所以只能显示初始化
4、boost::weak_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #include<boost/smart_ptr.hpp> 便可以使用。
5、boost::scoped_ptr:属于 boost 库,定义在 namespace boost 中,包含头文件 #include<boost/smart_ptr.hpp>
6、boost::shared_ptr:属于 boost 库,定义在 namespace boost 中,包含头文件 #include<boost/smart_ptr.hpp> 便可以使用。
是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。
7、boost::scoped_array:属于 boost 库,定义在 namespace boost 中,包含头文件 #include<boost/smart_ptr.hpp> 便可以使用。
便是用于 管理动态数组的。跟 boost::scoped_ptr 一样,也是独享所有权的。
8、boost::shared_array:属于 boost 库,定义在 namespace boost 中,包含头文件 #include<boost/smart_ptr.hpp> 便可以使用。
9、boost::intrusive_ptr:属于 boost 库,定义在 namespace boost 中,包含头文件 #include<boost/smart_ptr.hpp> 便可以使用。
总结:
1、在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想,而且极容易出错[2]。
2、在确定对象无需共享的情况下,使用 boost::scoped_ptr(当然动态数组使用 boost::scoped_array)。
3、在对象需要共享的情况下,使用 boost::shared_ptr(当然动态数组使用 boost::shared_array)。
4、在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下,使用 boost::weak_ptr,一般常用于软件框架设计中。
5、最后一点,也是要求最苛刻一点:在你的代码中,不要出现 delete 关键字(或 C 语言的 free 函数),因为可以用智能指针去管理。
<三>标准模板库---STL(Standard Template Library)
STL包含:容器类(container)、迭代子(iterator)、 算法(algorithm)
泛型算法(generic algorithm)和函数对象(function object)使算法摆脱了对不同数据类型个性操作的依赖。
1、容器类(Container):-容器类是管理序列的类,是容纳一组对象或对象集的类。通过由容器类提供的成员函数,可以实现诸如向序列中插入元素,删除元素,查找元素等操作,这些成员函数通过返回迭代子来指定元素在序列中的位置。
容器分为三大类:顺序容器、关联容器、容器适配器
http://www.cnblogs.com/luoxn28/p/5671988.html
(1)矢量类(vector):矢量(vector)类提供具有连续内存地址的数据结构,可通过下标运算符[ ]直接有效地访问矢量的任何元素。
1)#include<vector>--->vector<type> vi; //type为类型
2)构造函数:vector(size_t n)构造n个元素、vector(size_t n,T& V)为每个元素用V初始化...
3)常用函数:empty()判空、begin()返回首地址、end()返回末元素下一个、clear()清空
front()第一个数据、back()最后一个数据、size()数据个数、push_back(elem)插入尾部
pop_back()删除尾部数据。swap()交换两容器数据
(2)列表类:列表类是由双向链表组成的。它有两个指针域,可以向前也可以向后进行访问,但不能随机访问,即支持的迭代子类型为双向迭代子。列表类不能使用下标运算符[]。它定义在头文件<list>中。
1)#include<list>---->list<type> ls;
2)常用函数:大多与vector类似,
insert(iter, param)在iter位置插入param,
insert(iter, n, param)在iter位置插入n个param
insert(iter, ls2.gegin(), ls2.end())在iter位置插入ls2从头到尾的数据
但只能通过迭代器访问,不能用[]
(3)映射map:
1)#include<map> --->map<type1, type2> mp;-->mp.insert(pair<type1, type2>(1, 20 ));//
2) map<string , int >mapstring; map<int ,string >mapint;
map<sring, char>mapstring; map< char ,string>mapchar;
map<char ,int>mapchar; map<int ,char >mapint;
3)常用函数:find()查找一个元素
(4)迭代器iterator:每个容器对象都有自身相应的迭代器
1)iterator除了进行++,--操作,可以将iter+n,iter-n赋给一个新的iteraor对象。还可以使用一个iterator减去另外一个iterator.
如:vector<type> vi;-->vector<type>::iterator iter; for(iter = vi.gegin(); iter < vi.end(); iter++){cout<<*iter<<endl;}//可用list、map等其他容器
2、泛型算法(Generic Algorithm):-在模板中算法不依赖于具体的数据类型,而泛型算法更进一步不依赖于具体的容器。
//泛型算法中采用函数对象(function object)引入不同情况下同一算法的差异。它没有使用继承和多态,避免了虚函数的开销,使STL效率更高
1)
3、迭代子(Iterator):-迭代子是指针概念的泛型化,它指向容器中的元素,它能象指针一样增减,轮流指示容器中每个元素。所以说迭代子是面向对象版本的指针。迭代子可以包括指针,但迭代子又不仅仅是一个指针。
//迭代子把算法与容器连接起来。算法只是间接通过迭代子操作容器元素,算法本身与容器无关。算法通常返回迭代子。
/*------------------------------------第十七章、文件的读写---------------------------------*/
<一>流类体系以及各种控制
//最重要的两部分功能为标准输入/输出(standard input/output)和文件处理。
//在C++的流类库中定义了四个全局流对象:cin,cout,cerr和clog。可以完成人机交互的功能。
//要使用这四个功能,必须包含<iostream.h>文件
1、cin:-标准输入流对象,键盘为其对应的标准设备。带缓冲区的,缓冲区由streambuf类对象来管理。(使用streambuf是因为屏幕相对CPU处理较慢,使用它来配合CPU提高效率)
2、cout:-标准输出流对象,显示器为标准设备。带缓冲区的,缓冲区由streambuf类对象来管理。
3、cerr和clog:-标准错误输出流,输出设备是显示器。为非缓冲区流,一旦错误发生立即显示。
4、格式控制符:-使用格式控制符,可以进行格式化输入输出。这些格式对所有文本方式的输入输出流均适用。
(1)取多种控制时,用或“|”运算符来合成,合成为一个长整型数,在ios中为:long x_flags;
cout::flags(ios::left|ios::....);//如此设置格式
enum{
skipws=0x0001, //跳过输入中的空白字符
left=0x0002, //输出左对齐
right=0x0004, //输出右对齐
internal=0x0008, //在输出符号或数制字符后填充
dec=0x0010, //在输入输出时将数据按十进制处理
oct=0x0020, //在输入输出时将数据按八进制处理
hex=0x0040, //在输入输出时将数据按十六进制处理
showbase=0x0080, //在输出时带有表示数制基的字符
showpoint=0x0100, //输出浮点数时,必定带小数点
uppercase=0x0200, //输出十六进制,用大写
showpos=0x0400, //输出正数时,加”+”号
scientific=0x0800, //科学数方式输出浮点数
fixed=0x1000, //定点数方式输出实数
unitbuf=0x2000, //插入后,立即刷新流
stdio=0x4000 //插入后,立即刷新stdout和stderr
}
(2)流操作子(setiosflags stream manipulator)
流格式控制成员函数的使用比较麻烦,可改用流操作子(Setiosflags Stream Manipulator)。例如setw()等,可代替流类中的格式控制成员函数。
cout<<left|oct...<<param<<endl;//left/oct/endl...都是流操作子
5、标准输入/输出成员函数
输入流成员函数声明:
(1)int istream::get():提取一个字符,包括空格,制表,backspace和回车等。
istream&istream::get(char &);istream&istream::get(unsigned char &);
(2)istream&istream::get(char *,int,char=’\n’):提取的串放在第一个参数为开始地址的存储区(不查边界);第二个参数为至多提取的字符个数(指定为n,最多取n-1个,再加一个字符串结束符);第三个参数为结束字符,遇此字符则结束,默认为回车换行符。
istream&istream::get(unsigned char *,int,char=’\n’);
istream&istream::getline(char *,int,char=’\n’);
istream&istream::getline(unsigned char *,int,char=’\n’);
(3)int istream::gcount():函数gcount()返回最后一次提取的字符数量,包括回车
(4)istream&istream::ignore(int=1,int=EOF):第一个参数为要提取的字符数量,默认为1;第二个参数为结束字符,提取该结束字符,但对所提取的字符不保存不处理,作用是空读。第二个参数的默认值EOF为文件结束标志。
输出流成员函数声明:
ostream&ostream::put(char);
//输出参数字符
ostream&ostream::put(unsigned char);
ostream&ostream::put(signed char);
ostream&ostream::flush();
//刷新一个输出流,用于cout和clog
<二>文件的打开与关闭
1、类型
C++把每个文件都看成一个有序的字节流,每一个文件或者以文件结束符(end of file marker)结束,或者在特定的字节号处结束.
当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个文件定位指针(file position pointer)的控制。
(1)文本文件:由字符序列组成,在文本文件中存取的最小信息单位为字符(character),也称ASCII码文件。
(2)二进制文件:存取的最小信息单位为字节(Byte)。
2、文件使用的5步骤:
(1)说明一个文件流对象,这又被称为内部文件:
1)ifstream ifile;//只输入用
2)ofstream ofile;//只输出用
3)fstream iofile;//既输入又输出用
(2)使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。
1)void ifstream::open(const char*,int=ios::in,int=filebuf::openprot);
2)void ofstream::open(const char*,int=ios::out,int=filebuf::openprot);
3)void fstream::open(const char*,int,int=filebuf::openprot);
第一个参数为要打开的磁盘文件名。第二个参数为打开方式,有输入(in),输出(out)等,打开方式在ios基类中定义为枚举类型。第三个参数为指定打开文件的保护方式,一般取默认
上面三个文件流类都重载了一个带默认参数的构造函数,功能与open函数一样:
所以(1)和(2)两步可合成: fstream iofile(”myfile.txt”,ios::in|ios::out);
(3)打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址。
因此打开一个文件完整的程序为:
fstream iofile(”myfile.txt”,ios::in|ios::out);
if(!iofile){//文件指针为空,打开失败}
(4)使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。
1)
(5)关闭文件。三个文件流类各有一个关闭文件的成员函数 :
1)void ifstream::close();
1)void ofstream::close();
1)void fstream::close();
关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。
关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。
4、文本文件的读写
(1)文本文件的顺序读写:顺序读写可用C++的提取运算符(>>)和插入运算符(<<)进行。
(2)二进制文件的读写:
istream&istream::read(char *,int);
istream&istream::read(unsigned char*,int);
istream&istream::read(signed char *,int);
//第一个参数指定存放有效输入的变量地址,第二个参数指定提取的字节数,
ostream&ostream::write(const char *,int);
ostream&ostream::write(const unsigned char *,int);
ostream&ostream::write(const signed char *,int);
5、文件结束判断:-读函数并不能知道文件是否结束,可用状态函数int ios::eof()来判断文件是否结束。必须指出系统是根据当前操作的实际情况设置状态位,如需根据状态位来判断下一步的操作,必须在一次操作后立即去调取状态位,以判断本次操作是否有效。
6、二进制文件优点:-可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。文件结束后,系统不会再读(见eofbit的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。
7、文件的随机访问:
enum seek_dir
{
beg=0, //文件开头
cur=1, //文件指针的当前位置
end=2 //文件结尾
};
(1)istream&istream::seekg(streampos); //指针直接定位
istream&istream::seekg(streamoff, ios::seek_dir); //指针相对定位
long istream::tellg(); //返回当前指针位置
//iofile.seekbg(-20L, ios::cur) 表示从当前位置前移20字节
8、设置“输出流指针控制字”的成员函数:
(1)ostream&ostream::seekp(streampos);
ostream&ostream::seekp(streamoff,ios::seek_dir);
long ostream::tellp();
函数名中g是get的缩写,而p是put的缩写。对输入输出文件定位指针只有一个但函数有两组,这两组函数功能完全一样。
/*------------------------------------第十七章、计算系统---------------------------------*/
/*------------------------------------总结---------------------------------*/
<一>排序算法
1、冒泡排序法:最坏的情况下的比较次数是O(N^2)
bool m_BubbleSort(Type *pDate, int num)//升序
{
for (int i=0; i< num; i++)
{
for (int j = i; j<num; j++)
{
if (pDate[i] > pDate[j])
{
Type temp = pDate[i];
pDate[i] = pDate[j];
pDate[j] = temp;
}
}
}
return true;
}
2、插入排序法:按顺序将序列排列,每次将当前要排的元素与已排好序列对比后插入有序位置。
时间复杂度O(n2)
最差情况:反序,需要移动n*(n-1)/2个元素
最好情况:正序,不需要移动元素
bool m_InsertSort(Type *pDate, int num)//升序
{
int i, j;
int n = num;
int target;
for (i = 1; i < n; i++)
{
j = i;
target = pDate[i];
while (j > 0 && target < pDate[j - 1])
{
pDate[j] = pDate[j - 1];
j--;
}
pDate[j] = target;
}
return true;
}
3、选择排序法:从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推,就可以完成整个的排序工作了
//升序排列
bool m_SelectSort(Type *pDate, int len)
{
for(int i=0; i<len; i++){
int min = pDate[i];
int temp;
int index = i;
for(int j=i+1;j<len;j++){
if(pDate[j] < min){
min = pDate[j];
index = j;
}
}
temp = pDate[i];
pDate[i] = min;
pDate[index]= temp;
}
return true;
}
选择排序算法复杂度是O(n^2)。
4、快速排序:快速排序快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n^2)。
//快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
5、归并排序:归并排序的时间复杂度是O(nlog2n)。
//。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
6、堆排序:堆排序算法时间复杂度O(nlogn)。
0 0
- C基础学习总结
- C语言基础学习小总结
- object-c 基础动画的学习总结
- 学习笔记---C程序结构、C语言基础语句总结
- C++基础学习总结0001
- linux c 基础学习总结之文件IO
- C语言基础总结
- C基础总结
- C语言--基础总结
- C语言基础总结
- C语言基础总结
- C语言基础总结
- C基础点总结
- c语言基础总结
- Perl基础学习总结
- jQuery基础学习总结
- JavaScript基础学习总结
- Java基础学习总结
- Ant 打包步骤
- 面试
- Unix网络编程之回射客户端-client.c
- jQuery入门03--选择元素与理解结果集
- Reveal2 使用小技巧
- C++基础学习总结0001
- 蓝桥杯 振兴中华 dfs入门 TWT Tokyo Olympic 2combo-3
- javascript: location对象
- 计算机网络基础3
- ByteBuffer的心得
- 一个智能指针的实现
- React Native开发原生Android,IOS教程
- (iOS开发)tableview自带的删除方法(8.0之后适用)
- unity地形之splatalpha研究 地形贴图导出更换与绘制