C++基础知识点(3)

32. 函数模板与类模板

template<typename T> void 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<typename T1, typename T2>  //这里不能有分号
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<typename T1, typename T2>
T1 Point<T1, T2>::getX() const { //类名Point后面也要带上类型参数
    return m_x;
}
template<typename T1, typename T2>
T2 Point<T1, T2>::getY() const{
    return m_y;
}
//创建对象
//Point<int, int> 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 已经将它抛弃了,不再建议使用

  • 派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格
  • 异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致

C++ exception类层次图

35. 拷贝构造函数

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 对象传递给形参

36. 浅拷贝和深拷贝

调用默认的拷贝构造函数就是浅拷贝,对于简单的类,默认的拷贝构造函数一般够用。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,将对象所持有的其他资源一并拷贝的行为深拷贝

例如,一个类中的成员变量包含指针,如果只是进行浅拷贝,那么就此对象的指针地址复制给新的对象,导致这两个对象的指针指向了同一块内存,那么就会相互影响。因此要进行深拷贝,将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。

另外一种需要深拷贝的情况就是在创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等。

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 类型的实参
  • 赋值运算符重载函数除了能有对象引用这样的参数之外,也能有其它参数,但是其它参数必须给出默认值

38. 再谈构造函数

  • 默认构造函数。就是编译器自动生成的构造函数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)。

41. 输入流和输出流

img

cout、cerr和clog之间的区别:

  • cout 除了可以将数据输出到屏幕上,通过重定向(后续会讲),还可以实现将数据输出到指定文件中;而cerr 和 clog 都不支持重定向,它们只能将数据输出到屏幕上
  • cout 和 clog 都设有缓冲区,即它们在输出数据时,会先将要数据放到缓冲区,等缓冲区满或者手动换行(使用换行符 \n 或者 endl)时,才会将数据全部显示到屏幕上;而 cerr 则不设缓冲区,它会直接将数据输出到屏幕上

表:cin输入流对象常用成员方法

成员方法名功能
istream & getline(char* buf, int bufSize);从输入流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 ‘\0’。
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<<重载 << 运算符,使其用于输出其后指定类型的数据。
#include<iostream>
int main(){
    char url[10] = {0};
    std::cin.getline(url, 30); //读取一行字符串
    std::cout<<cin.gcount()<<endl; //输出上一条语句读取字符个数
    std::cout.write(url, 30); //输出上一条语句读取字符串个数
    return 0;
}
#include <iostream> //cin 和 cout
#include <fstream> //文件输入输出流
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;
}
#include <iostream>
using namespace std;
int main()
{
    int c;
    while ((c = cin.get()) != EOF) //EOF 是在 iostream 类中定义的一个整型常量,值为 -1
        cout.put(c);
    return 0;
}

42. 文件流类

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 为 ‘\0’。
ignore(n,ch)从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。
peek()返回文件流中的第一个字符,但并不是提取该字符。
putback©将字符 c 置入文件流(缓冲区)。
operator<<fstream ofstream重载 << 运算符,用于向文件中写入指定数据。
put()向指定文件流中写入单个字符。
write()向指定文件中写入字符串。
tellp()用于获取当前文件输出流指针的位置。
seekp()设置输出文件输出流指针的位置。
flush()刷新文件输出流缓冲区。
good()fstream ofstream ifstream操作成功,没有发生任何错误。
eof()到达输入末尾或文件尾。
#include<iostream>
#include<fstream>
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;
}

43. 打开文件

1.使用 open 函数打开文件

void open(const char* szFileName, int mode)

第一个参数是指向文件名的指针,第二个参数是文件的打开模式标记。

模式标记适用对象作用
ios::inifstream fstream打开文件用于读取数据。如果文件不存在,则打开出错。
ios::outofstream fstream打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。
ios::appofstream fstream打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。
ios::ateifstream打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。
ios:: truncofstream打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。
ios::binaryifstream ofstream fstream以二进制方式打开文件。若不指定此模式,则以文本模式打开。
ios::in | ios::outfstream打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::outofstream打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out | ios::truncfstream打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。

2.使用流类的构造函数

#include<iostream>
#include<fstream>
int main(){
    std::ifstream inFile("c:\\tmp\\test.txt", ios::in);
    if(inFile){
        inFile.close();
    }else{
        cout<<"test.txt doesn't exist"<<endl;
    }
    return 0;
}

44. 读写文本文件

1.>>和<<读写文本文件

#include<iostream>
#include<fstream>
using namespace std;
int main(){
    ifstream srcFile("in.txt", ios::in); //以读模式打开文件
    ofstream destFile("out.txt", ios::out); //以写模式打开文件
    while(srcFIle >> x){
        destFile<<x<<" ";
    }
    destFile.close();
    srcFile.close();
}

2.read()和write()读写二进制文件

ostream & write(char* buffer, int count);

istream & read(char* buffer, int count);

buffer 用于指定要写入文件的二进制数据的起始位置;count 用于指定写入字节的个数。

#include<iostream>
#include<fstream>
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<<s.m_name<<" "<<s.m_age<<endl;
    }
    inFile.close();
    return 0;
}

45. get()和put()读写文件

ostream& put (char c);

int get(); //返回读取字符的 ASCII 码
istream& get (char& c);

#include <iostream>
#include <fstream>
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<<c;
    }
    inFile.close();
    return 0;
}

46. getline()读取一行

istream & getline(char* buf, int bufSize);
istream & getline(char* buf, int bufSize, char delim);

第一种语法格式用于从文件输入流缓冲区中读取 bufSize-1 个字符到 buf,或遇到 \n 为止(哪个条件先满足就按哪个执行),该方法会自动在 buf 中读入数据的结尾添加 ‘\0’。

第二种语法格式和第一种的区别在于,第一个版本是读到 \n 为止,第二个版本是读到 delim 字符为止。\n 或 delim 都不会被读入 buf,但会被从文件输入流缓冲区中取走。

#include <iostream>
#include <fstream>
using namespace std;
int main(){
    char c[40]; //建立一个buf缓冲区
    ifstream inFile("in.txt", ios::in | ios::binary);
    //读取文件中的多行数据
    while(inFile.getline(c, 40)){
        cout<<c<<endl;
    }
    inFile.close();
    return 0;
}

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值