templatevoid Swap(T &a, T &b){ //typename关键字也可以使用class关键字替代,它们没有任何区别 T temp = a; a = b; b = temp; } int n1 = 100, n2 = 200; Swap(n1, n2); float f1 = 12.5, f2 = 56.93; Swap(f1, f2);
template//这里不能有分号 class Point{ public: Point(T1 x, T2 y): m_x(x), m_y(y){ } public: T1 getX() const; //获取x坐标 T2 getY() const; //获取y坐标 private: T1 m_x; //x坐标 T2 m_y; //y坐标 }; template T1 Point ::getX() const { //类名Point后面也要带上类型参数 return m_x; } template T2 Point ::getY() const{ return m_y; } //创建对象 //Point p1(10, 20);
C++是一种强类型语言,一旦为变量指明了某种数据类型,该变量以后就不能赋予其他类型的数据了,除非经过强制类型转换或隐式类型转换。所以C++比较“死板”,后来 C++ 开始支持模板了,模板所支持的类型是宽泛的,没有限制的,我们可以使用任意类型来替换,这种编程方式称为泛型编程。
33. 捕获异常try{
//可能抛出异常的语句,必须将异常明确地抛出,try 才能检测到
}catch(exceptionType variable1){ //catch(exceptionType){
//处理异常的语句
}catch(exceptionType variable2){ // 多级catch
//处理异常的语句
}
只能等到程序运行后,真的抛出异常了,再将异常类型和 catch 能处理的类型进行匹配(在匹配过程中仅可以出现「向上转型」、「const 转换」和「数组或函数指针转换」),匹配成功的话就“调用”当前的 catch,否则就忽略当前的 catch。如果不希望 catch 处理异常数据,也可以将 variable 省略掉。
34. 抛出异常throw->try->catch
throw exceptionData; double func (char param) throw (int);//只能抛出 int 类型的异常 double func (char param) throw (int, char, exception); //函数会抛出多种类型的异常
exceptionData 可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类。throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范,有些教程也称为异常指示符或异常列表。异常规范是 C++98 新增的一项功能,但是后来的 C++11 已经将它抛弃了,不再建议使用。
- 派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格
- 异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致
class Student{
public:
Student(const Student &stu); //声明拷贝构造函数
};
Student::Student(const Student &stu){ //定义
this->m_name = stu.m_name;
this->m_age = stu.m_age;
this->m_score = stu.m_score;
}
拷贝构造函数只有一个参数,它的类型是当前类的引用,而且一般都是 const 引用。
- 如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,陷入死循环
- 拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据。此外,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了
调用默认的拷贝构造函数就是浅拷贝,对于简单的类,默认的拷贝构造函数一般够用。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,将对象所持有的其他资源一并拷贝的行为叫深拷贝。
例如,一个类中的成员变量包含指针,如果只是进行浅拷贝,那么就此对象的指针地址复制给新的对象,导致这两个对象的指针指向了同一块内存,那么就会相互影响。因此要进行深拷贝,将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。
另外一种需要深拷贝的情况就是在创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等。
37. 重载=在定义的同时进行赋值叫做初始化(Initialization),定义完成以后再赋值(不管在定义的时候有没有赋值)就叫做赋值(Assignment)。初始化只能有一次,赋值可以有多次。
class Array{
public:
Array();
Array(const Array &array);
Array & operator=(const Array &array);
~Array();
private:
int m_len;
int *m_p;
};
Array::Array(int len): m_len(len){
m_p = (int *)calloc(len, seizeof(int));
}
Array::~Array(){free(m__p);}
Array::Array(const Array &arr){
this->m_len = arr.m_len;
this->m_p = (int*)malloc(this->m_len, sizeof(int));
memcpy(this->m_p, arr.m_p, m_len*sizeof(int));
}
Array &Array::operator=(const Array &arr){ //重载赋值运算符
if(this != &arr){ //判断是否是给自己赋值
this->m_len = arr.m_len;
free(this->m_p); //释放原来的内存
this->m_p = (int*)calloc(this->m_len, sizeof(int));
memcpy(this->m_p, arr.m_p, m_len*sizeof(int));
}
return *this;
}
- operator=() 的返回值类型为Array &,能够避免在返回数据时调用拷贝构造函数,还能够达到连续赋值的目的
- return *this表示返回当前对象(新对象)
- operator=() 的形参类型为const Array &,这样不但能够避免在传参时调用拷贝构造函数,还能够同时接收 const 类型和非 const 类型的实参
- 赋值运算符重载函数除了能有对象引用这样的参数之外,也能有其它参数,但是其它参数必须给出默认值
- 默认构造函数。就是编译器自动生成的构造函数Complex();//没有参数
- 普通构造函数。就是用户自定义的构造函数Complex(double real, double imag);//两个参数
- 拷贝构造函数。在以拷贝的方式初始化对象时调用Complex(Const Complex &c);
- 转换构造函数。将其它类型转换为当前类类型时调用Complex(dobule real);//转换构造函数只有一个参数
除了拷贝构造函数,其他三个构造函数可以精简成一个Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag) {}。
39. 类型转换函数类型转换函数的作用就是将当前类类型转换为其它类型,它只能以成员函数的形式出现,也就是只能出现在类中,类型转换函数没有参数。
operator returnType(){ //因为要将当前对象转为其他类型,参数不言而喻,类型转换函数没有参数
//TODO;
return data; //这里返回的data就要返回的returnType类型
}
class Complex{
public:
Complex(): m_real(0.0), m_imag(0.0){ }
operator double() const{return m_real;} //将Complex类型转为double类型
private:
double m_real;
double m_image;
};
int main(){
Complex c1(1.1, 2);
double f = c1; //相当于double f = Complex::operator double(&c1),
}
40. 四种类型转换运算符
newType variable = xxx_cast (data);
| 关键字 | 说明 |
|---|---|
| static_cast | 用于良性转换,一般不会导致意外发生,风险很低。不能用于两个具体类型指针之间的转换;int和指针之间的转换。 |
| const_cast | 用于const 转非 const、volatile 转非 volatile之间的转换。 |
| reinterpret_cast | 高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。 |
| dynamic_cast | 借助运行时类型识别( RTTI),用于类型安全的向下转型(Downcasting)。 |
cout、cerr和clog之间的区别:
- cout 除了可以将数据输出到屏幕上,通过重定向(后续会讲),还可以实现将数据输出到指定文件中;而cerr 和 clog 都不支持重定向,它们只能将数据输出到屏幕上
- cout 和 clog 都设有缓冲区,即它们在输出数据时,会先将要数据放到缓冲区,等缓冲区满或者手动换行(使用换行符 n 或者 endl)时,才会将数据全部显示到屏幕上;而 cerr 则不设缓冲区,它会直接将数据输出到屏幕上
表:cin输入流对象常用成员方法
| 成员方法名 | 功能 |
|---|---|
| istream & getline(char* buf, int bufSize); | 从输入流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 ‘ ’。 |
| int get(); | 从输入流中读取一个字符,同时该字符会从输入流中消失。 |
| gcount() | 返回上次从输入流提取出的字符个数,该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。 |
| peek() | 返回输入流中的第一个字符,但并不是提取该字符。 |
| putback© | 将字符 c 置入输入流(缓冲区)。 |
| ignore(n,ch) | 从输入流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。 |
| operator>> | 重载 >> 运算符,用于读取指定类型的数据,并返回输入流对象本身。 |
表:cout输出流对象常用成员方法
| 成员方法名 | 功能 |
|---|---|
| ostream&put(char c); | 用于向输出流缓冲区中添加单个字符。 |
| ostream&write(const char *s,streamsize n); | 输出指定的字符串。 |
| streampos tellp(); | 用于获取当前输出流缓冲区中最后一个字符所在的位置。 |
| ostream& seekp (streampos pos); | 设置输出流指针的位置。 |
| flush() | 刷新输出流缓冲区。 |
| operator<< | 重载 << 运算符,使其用于输出其后指定类型的数据。 |
#includeint main(){ char url[10] = {0}; std::cin.getline(url, 30); //读取一行字符串 std::cout< #include//cin 和 cout #include //文件输入输出流 using namespace std; int main() { //定义一个文件输出流对象 ofstream outfile; //打开 test.txt,等待接收数据 outfile.open("test.txt"); const char * str = "http://c.biancheng.net/cplus/"; //将 str 字符串中的字符逐个输出到 test.txt 文件中,每个字符都会暂时存在输出流缓冲区中 for (int i = 0; i < strlen(str); i++) { outfile.put(str[i]); //获取当前输出流 } cout << "当前位置为:" << outfile.tellp() << endl; //调整新进入缓冲区字符的存储位置 outfile.seekp(23); //等价于: //outfile.seekp(23, ios::beg); //outfile.seekp(-6, ios::cur); //outfile.seekp(-6, ios::end); cout << "新插入位置为:" << outfile.tellp() << endl; const char* newstr = "python/"; outfile.write("python/", 7); //关闭文件之前,刷新 outfile 输出流缓冲区,使所有字符由缓冲区流入test.txt文件 outfile.close(); return 0; } #include42. 文件流类using namespace std; int main() { int c; while ((c = cin.get()) != EOF) //EOF 是在 iostream 类中定义的一个整型常量,值为 -1 cout.put(c); return 0; } fstream类常用成员方法:
成员方法名 适用类对象 功 能 open() fstream ifstream ofstream 打开指定文件,使其与文件流对象相关联。 is_open() 检查指定文件是否已打开。 close() 关闭文件,切断和文件流对象的关联。 swap() 交换 2 个文件流对象。 operator>> fstream ifstream 重载 >> 运算符,用于从指定文件中读取数据。 gcount() 返回上次从文件流提取出的字符个数。该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。 get() 从文件流中读取一个字符,同时该字符会从输入流中消失。 getline(str,n,ch) 从文件流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 ‘ ’。 ignore(n,ch) 从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。 peek() 返回文件流中的第一个字符,但并不是提取该字符。 putback© 将字符 c 置入文件流(缓冲区)。 operator<< fstream ofstream 重载 << 运算符,用于向文件中写入指定数据。 put() 向指定文件流中写入单个字符。 write() 向指定文件中写入字符串。 tellp() 用于获取当前文件输出流指针的位置。 seekp() 设置输出文件输出流指针的位置。 flush() 刷新文件输出流缓冲区。 good() fstream ofstream ifstream 操作成功,没有发生任何错误。 eof() 到达输入末尾或文件尾。 #include43. 打开文件#include int main(){ const char *url = "www.baidu.com"; std::fstream fs; fs.open("test.txt", std::ios::out); fs.write(url, 6); fs.close(); return 0; } 1.使用 open 函数打开文件
void open(const char* szFileName, int mode)
第一个参数是指向文件名的指针,第二个参数是文件的打开模式标记。
模式标记 适用对象 作用 ios::in ifstream fstream 打开文件用于读取数据。如果文件不存在,则打开出错。 ios::out ofstream fstream 打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。 ios::app ofstream fstream 打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。 ios::ate ifstream 打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。 ios:: trunc ofstream 打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。 ios::binary ifstream ofstream fstream 以二进制方式打开文件。若不指定此模式,则以文本模式打开。 ios::in | ios::out fstream 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 ios::in | ios::out ofstream 打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 ios::in | ios::out | ios::trunc fstream 打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。 2.使用流类的构造函数
#include#include int main(){ std::ifstream inFile("c:\tmp\test.txt", ios::in); if(inFile){ inFile.close(); }else{ cout<<"test.txt doesn't exist"< 44. 读写文本文件 1.>>和<<读写文本文件
#include#include using namespace std; int main(){ ifstream srcFile("in.txt", ios::in); //以读模式打开文件 ofstream destFile("out.txt", ios::out); //以写模式打开文件 while(srcFIle >> x){ destFile< 2.read()和write()读写二进制文件
ostream & write(char* buffer, int count);
istream & read(char* buffer, int count);
buffer 用于指定要写入文件的二进制数据的起始位置;count 用于指定写入字节的个数。
#include#include using namespace std; class CSstudent{ public: char m_name[20]; int m_age; }; int main(){ CSstudent s; ofstream outFile("students.dat", ios::out|ios::binary); while(cin>>s.m_name>>s.m_age) outFile.write((char*)&s, sizeof(s)); outFile.close(); ifstream inFile("students.dat",ios::in|ios::binary); //二进制读方式打开 if(!inFile){ inFile.close(); return 0; } while(inFile.read((char*)&s, sizeof(s))){ cout< 45. get()和put()读写文件 ostream& put (char c);
int get(); //返回读取字符的 ASCII 码
istream& get (char& c);#include#include using namespace std; int main(){ char c; ofstream outFile("out.txt", ios::out | ios::binary); while(cin>>c){ outFile.put(c); } //这里以 Ctrl+Z 的组合键,表示输入结束 outFile.close(); ifstream inFile("out.txt", ios::in | ios::binary); while((c==inFile.get()) && c!=EOF){ cout< 46. getline()读取一行 istream & getline(char* buf, int bufSize);
istream & getline(char* buf, int bufSize, char delim);第一种语法格式用于从文件输入流缓冲区中读取 bufSize-1 个字符到 buf,或遇到 n 为止(哪个条件先满足就按哪个执行),该方法会自动在 buf 中读入数据的结尾添加 ‘ ’。
第二种语法格式和第一种的区别在于,第一个版本是读到 n 为止,第二个版本是读到 delim 字符为止。n 或 delim 都不会被读入 buf,但会被从文件输入流缓冲区中取走。
#include#include using namespace std; int main(){ char c[40]; //建立一个buf缓冲区 ifstream inFile("in.txt", ios::in | ios::binary); //读取文件中的多行数据 while(inFile.getline(c, 40)){ cout< 47. 移动和获取文件读写指针 在读写文件时,希望直接跳到文件中的某处开始读写,需要仙剑文件的读写指针指向该处。
ostream & seekp (int offset, int mode); //设置文件写指针的文职
istream & seekg (int offset, int mode); //设置文件读指针的位置mode三种选项:
- ios::beg:让文件读指针(或写指针)指向从文件开始向后的 offset 字节处
- ios::cur:在此情况下,offset 为负数则表示将读指针(或写指针)从当前位置朝文件开头方向移动 offset 字节
- ios::end:让文件读指针(或写指针)指向从文件结尾往前的 |offset|(offset 的绝对值)字节处
参考int tellp(); //返回文件写指针位置
int tellg(); //返回文件读指针位置1.http://c.biancheng.net/cplus/



