1. 类的定义
编译器一般分两步处理类:首先编译成员的声明,然后处理成员函数体。
默认构造函数:当我们的类没有显示的定义构造函数,那么编译器会为我们隐式的顶一个一个“合成的默认构造函数”(并非所有的编译器都如此),其按照如下规则初始化类的数据成员:
如果存在类内的初始值,这用初始值初始化成员
否则,默认初始化该成员
构造函数初始值列表,即构造函数冒号与花括号之间的部分:
Sales(const std::string&s):bookNo(s){}
但某个数据成员被构造函数初始化列表忽略时,其将以与合成默认构造函数相同的方式隐式初始化。在C++中,我们使用访问说明符加强类的封装:
定义在public后的成员在整个程序内可被访问,其主要用于定义类的接口
定义在private下的成员可以被类的成员函数访问,但不能被使用该类的代码访问。
class和struct两个关键字都可以定义类,唯一的区别是:struct默认的访问权限为public,而class为private。
当我们希望其他类或者函数可以访问类的非公有成员时,我们勊将其他类或者函数定义成为类的友元。友元的声明只能出现在类的内部,但具体位置不限,其标志是friend关键字。
部分编译器允许在尚无友元函数的初始生命的情况下就调用它,不过最好加上独立的函数声明,进而支持跨平台性。
2.类的其他特性
定义一个类型成员:
class{
public :
typedef std::string::size_type pos;//这里我们定义了一个类型成员
private:
pos height;
}
需要注意的是,用于定义类型的成员必须先定义后使用,因此类型成员通常出现在类开始的地方。定在在类内部的成员函数是自动inline的,我们既可以在类内把inline作为声明的一部分显示的声明成员函数,同样也可以在类的外部用inline关键字修饰函数的定义。尽管我们可以在生命和定义处同时添加inline,但最好只在外部定义是添加。
mutable关键字声明的变量表示该成员在任何情况下都可以被修改,即使在const成员函数内部。一个可变数据成员永远不可能是const的,即使他属于const对象的成员。
class Sample{
void some() const;//const 成员函数
mutable size_t access;//可变数据成员
};
void Sample::some() const{
access++;
}
一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
类的声明:
class Screen;
这类声明称为前向声明,对于Screen来说,它在声明之后定义之前是一个不完全类型,其只能在有限的情形下使用,可以定义该类型的引用或指针,也可以声明以该类型为返回值的函数,或用作函数参数。因为在定义之前,编译器不知道该类型需要多少内存空间。
如果一个类制定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员,必须注意,友元关系不存在传递性。
class Screen{
friend class Windows;
};
如果想令成员函数作为友元:
class Screen{
friend void Window::clear(int i);
};
类和非成员函数的声明不是必须在他们的友元声明之前,同时,即使我们仅仅是用声明友元的类的成员调用该友元函数,它必须被声明过的。
class X{
friend void f(){ //do something
}//友元函数可以定义在类的内部
};
void f();//但是必须声明!
如果一个成员函数在类定义外面定义,且其返回值为类的内部定义的类型,这需要给返回类型加上类作用域。
3. 构造函数
如果没有在构造函数的初始化列表中显式的初始化成员,则该成员将在构造函数体执行之前执行默认初始化,即构造函数体内执行的实际上是赋值而非初始化操作。但遇到const成员变量时,实际上必须在初始化列表中进行初始化。
简单来说,初始化和赋值方式 有两个区别:一个是底层效率问题,还有就是有些成员必须初始化。
成员的初始化顺序与他们在类定义中出现的顺序一致,而与初始化列表中出现的顺序无关,不过最好另初始化列表的顺序与其出现顺序一致。
我们可以为构造函数提供默认实参,如果一个构造函数为所有参数都提供了默认实参,这相当于其提供了一个默认构造函数。
class Sales{
Sales(std::string s = " "):books(s){};
string books;
};//相当于同时提供了一个默认构造函数
如果构造函数接受一个实参,这它实际上定义了转换为此类型的隐式转换机制,有时我们称这种构造函数为转换构造函数。
string book = “999-9999”;
item.combine(book)//combine的形参实际上为一个包含以string为参数的构造函数的类对象
一般来说,这种转换只允许一步类类型转换;不过这种转换不见得总是有效。
如果我们想要抑制这种类型的转换,可以在构造函数前加上explicit关键字,该关键字仅仅在类中声明函数时出现,在类外部定义时不应重复。一旦定义了explicit会影响拷贝初始化:
Sales item(null_book);//正确
Sales item2 = null_book;//错误
聚合类的定义,即但满足以下条件时,我们称类为聚合类:
A 所有成员都是public;
B 没有定义任何构造函数;
C 没有类内初始值;
D 没有积累,也没有virtual函数
struct Data{
int ival;
string s;
}
聚合类可以使用以下方式声明:
Data val2 = { 0,"Anna"};
花括号中值的顺序应与类中声明的顺序一致。4.类的静态成员
声明静态成员:我们通过在成员的声明之前加上static关键字,使其与类关联。
类的静态成员存在于任何对象中,为类的所有对象共享,同时静态成员函数也不与任何对象绑定在一起,它们不包含this指针。
静态成员函数不能是const,也不能在内部使用this,包括显示的使用this或者调用非静态成员这类隐式的使用。
但是,虽然静态成员(函数和变量)不属于某个对象,但我们仍可以通过类的对象,指针或者引用访问它们,当然还可以使用类作用域运算符。
当在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句。同时,我们必须在类的外部定义和初始化每个静态成员。
class Sales{
static int sample;
};
int Sales::sample = 1;
相比非静态成员,静态成员还可以被用作默认实参。
5. I/O 类常识
IO对象不能执行拷贝和赋值操作:
ofstream out1,out2;
out1 = out2; //不能对流对象赋值
ofstream print(ofstream); //不能初始化ofstream参数
out2 = print(out2); //不能拷贝流对象
C++通了一系列的变量和函数访问和操纵流的条件状态:
strm::iostate //基本形式
strm::badbit s.eof()// 表示流已崩溃,不可恢复错误
strm::failbit s.fail()// 指出一个IO操作失败了
strm::eofbit s.bad()// 流已经到了文件结束
strm::goodbit s.good()//表示流正常,此值为0
s.clear() //将流复位
s.clear(flag) //将指定位复位
s.setstate(flag) //将流置位
s.rdstate() //返回流的当前状态,返回类型为strm::iostate
一旦一个流发生错误,其后续的IO操作都会失败。确定一个流状态最简单的办法是将他当做一个条件来使用:
while(cin >> word)
//OK, 读成功
管理条件状态:
strm::iostate old = cin.rdstate();
cin.clear();
process_cin(cin);
cin.setstate(old);
每个输出流都会管理一个缓冲区,这样可以减少系统调用次数,提升性能,有很多原因可以导致缓冲区刷新:
程序正常结束,作为main 的return操作的一部分,缓冲区刷新被执行;
缓冲区满,需要刷新缓冲区,而后新数据才可以进入;
使用操作符“ endl" 来主动刷新;
在每个输出操作后,使用unitbuf操作符设置流内部状态,清空缓冲区,默认的cerr是设置unitbuf的,即错误输出都是立即刷新的,nounitbuf执行反向操作;
一个流被关联到另一个流,若一个输入流被关联到一个输出流,则任何读入操作都会先刷新输出流,标准库中cout和cin关联在一起的,使用tie函数可以绑定输入和输出流:
cin.tie(&cout);
使用flush或者ends,flush刷新但不输出,ends插入一个空字符后刷新缓冲区。
如果程序直接崩溃,输出缓冲区不会被刷新。
创建文件流对象时,若提供了一个文件名(即可以是string,也可以是C字符串),则open会自动被调用:
ifstream in(ifile);//创建对象,并打开文件
ofstream out ;// 未关联任何对象
out.open(file) ; // 关联到对象,若open失败,failbit会被置位
out.open(file);
if(out) // 检查状态,成功为1
我们需要注意,当一个文件流已经关联到某个文件,则想要关联别的文件,需要先关闭流,然后重新打开
out.close();
out.open(new_file);
当流对象被销毁时,其close函数会被自动调用。
每个流都会有一个关联的文件模式,一般来说输入流关联in模式,输出流关联out模式,输入输出流同时关联in & out模式。当然,我们也可以手动指定打开模式:
ofstream out;
out.open("a.txt",ofstream::app);
out.open("a.txt",ofstream::ate); //定位到末尾
out.open("a.txt",ofstream::binary);
out.open("a.txt",ofstream::trunk);
6. string流
sstream头文件定义了三个类型支持内存IO:istringstream从string读入数据,ostringstream向string写数据,stringstream读写string。stringstream除继承来的操作外,还有些特有的操作:
sstream strm;//未绑定string对象
sstream strm(S);//绑定string对象,保存S的一个拷贝,此函数为explicit
strm.str();//返回strm保存的string拷贝
strm.str(s);//strm.str(s);//将s拷贝到sstream对象中
如果我们想要清空一个sstring对象,可以使用:
strm.str("");