C++面向对象知识

353 阅读11分钟

一: 拷贝构造函数只会在初始化时调用

拷贝构造函数只会在初始化时调用,初始化之后的赋值,不会调用拷贝构造函数,而是简单的复制成员变量.

二、类型转换构造函数

image.png

image.png 可以看到,执行c1 = 9的赋值操作时,9会自动类型转换构造函数去生成一个临时的Complex对象.

image.png 其实我们可以看到,前面那个例子里面的那个构造函数,既可以作为隐式的类型转换,也可以作为显式的初始化,为了让编译器确切知道是哪种,引入了explicit关键词,加了explicit的就不能用于类型转换了,编译器会报错.

三、析构函数

image.png 变量生命期结束时,就会调用析构函数.

image.png new出来的这个对象是不会自动析构的,只有在delete的时候才会析构.

三、静态成员变量、函数

静态成员变量的初始化,必须要拿到class外面声明一下,声明的时候可以选择初始化或不初始化.

class Complex {
public:
    static int cc;
    double real,imag;
};

int Complex::cc = 1;

四、成员对象和封闭类

image.png

image.png

image.png

  • 有成员对象的话,必须写一个构造函数,不然不知道那个成员对象用什么构造函数进行初始化的
  • 成员对象要用初始化列表进行初始化.

image.png

五、常量对象、常量成员函数

image.png

  • 常量的对象只能使用构造、析构和const的常量函数.

image.png

  • const函数不能修改成员变量.

image.png

image.png

  • 这里很特别,是否const的函数居然是重载关系.

六、友元函数和友元类

image.png

  • 一个类的友元函数可以访问该类的私有成员

image.png

  • ModifyCar中之所以可以访问Car的私有成员price,就是因为规定了友元
  • MostExpensiveCar函数之所以可以...,也是应为规定了友元

image.png 下面这个代码,来规定CDriver类是CCar类的友元类

friend class CDriver; //声明CDriver为友元类

image.png

七、运算符重载

1.重载的基础知识

image.png

image.png

image.png

image.png

2.赋值运算符的重载

image.png

image.png

  • 可以看到,String的默认构造函数里面执行str(new char[1])str[0]=0,来形成一个空串
  • 重载赋值运算符,当被赋值的为char *的,那么就先delete []str,然后重新new一个,然后strcpy来得到
  • 要注意这个赋值函数的返回值是String &,我试了其他作为返回值都不行,暂时记住吧.

image.png

  • String s2 = "hello"这个是初始化语句,调用的是构造函数.

image.png 这个是浅拷贝,因为没有重载String到String的赋值,所以这个就是浅拷贝,拷贝这个str指针.这样会导致大灾难,如下:

image.png 之后S1或S2其中一个去用char*赋值的时候,就会把同一个str给delete掉,或者其中一个析构的时候,也会把他给delete掉.所以要加下面这样的代码:

image.png 但是这样还是有问题的,见下:

image.png 如果s=s的话,就把唯一一个str给delete掉了,就错误了!!!所以正确的办法是需要加一个检查,如果相等的话,那么直接返回了!


image.png 噢!这里解释了为什么要返回String&,是因为我们经常会连=来赋值

  • void不行,因为这样就不能连等
  • String不行,因为在执行(a=b)=c这样,其实是让a=c,我们需要让a=b的结果为a的引用,这样才能做到a=c

最后的问题是,如果不写复制构造函数,那么编译器缺省的那个是浅拷贝.

String( String & s) {
    str= new char[strlen(s.str)+1];
    strcpy(str,s.str);
}

3.运算符重载为友元函数

一般情况下,我们都会将运算符重载为class的成员函数,而不是全局函数,但是有时候必须是全局函数,比如在5+c的情况,我们总不可能给int做一个重载. 全局函数的话,如果要访问私有变量,那么只能弄成友元了.

image.png

image.png

image.png

4.流(插入/提取)运算符的重载

image.png

image.png

image.png

image.png

image.png

image.png

  • 做一个全局函数,class里面写<<重载如上,接收一个ostream & 和 CStudent类型的参数,然后用o << s.nAge来输出,然后返回这个ostream的引用

image.png

image.png ok,写之前,需要知道:

  • cin是istream类型的
  • cout是ostream类型的

image.png

  • cout是很简单的

image.png

  • cin比较复杂一点,不深究.

5.类型转换运算符 & 自增自减运算符 的重载

  • 类型转换运算符即类的名字,比如double可以将数据转换成double类型

image.png

    operator double() { // 类型转换的重载不需要写上返回类型的关键字double
        return real;
    }
  • 这个就是给double类型转换做一个重载,返回real. 可以看到,这个重载的写法有点特殊,不用写返回类型的,因为必须是double!
  • 不仅仅显示的(double)c做强制类型转换时会用到,隐式的double n = 2 + c也会用到

6.自增自减运算符的重载

image.png

image.png

  • ++i:作为一元的
  • i++:作为二元的,要多写一个没用的参数

image.png

#include <iostream>
using namespace std;

class CDemo {
private:
    int n;
public:
    CDemo(int n_=0) : n(n_) {

    }

    CDemo & operator++(); // 重载前置++
    CDemo operator++(int); // 重载后置++
    CDemo & operator--(); // 重载前置--
    CDemo operator--(int); // 重载后置--

    operator int() { // (int)类型强制转换
        return n;
    }
};

CDemo & CDemo::operator++() { // 重载前置++
    ++n;
    return *this;
}

CDemo CDemo::operator++(int) { // 重载后置++
    CDemo tmp(*this);
    n++;
    return tmp;
}

CDemo & CDemo::operator--() { // 重载前置--
    --n;
    return *this;
}

CDemo CDemo::operator--(int) { // 重载后置--
    CDemo tmp(*this);
    n--;
    return tmp;
}


int main()
{
    CDemo d(5);
    cout << (d++ ) << ","; //等价于 d.operator++(0);
    cout << d << ",";
    cout << (++d) << ","; //等价于 d.operator++();
    cout << d << endl;

    cout << (d-- ) << ","; //等价于 d.operator--(0);
    cout << d << ",";
    cout << (--d) << ","; //等价于 d.operator--();
    cout << d << endl;

    return 0;
}

输出:

5,6,7,7
7,6,5,5

注意:

  • 我们cout << d++,并没有重载<<,而是通过一个int强制转换,来搞成int.
  • 注意到,++d我们的返回值是CDemo &,是一个引用,因为我们经常会++i = 1. d++的返回值是CDemo,是一个值,因为我们不能i++ = 1,i++是返回一个临时的值,然后实际的值已经变了,所以看这个代码就很清楚了:
CDemo & CDemo::operator++() { // 重载前置++
   ++n;
   return *this;
}
CDemo CDemo::operator++(int) { // 重载后置++
   CDemo tmp(*this);
   n++;
   return tmp;
}

image.png

八、继承

1.继承和派生的概念

  • 继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)

    • 派生类是通过对基类进行修改和扩充得到的。
    • 在派生类中,可以扩充新的成员变量和成员函数。
    • 派生类一经定义后,可以独立使用,不依于基类。
  • 派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public

    • 在派生类的各个成员函数中,不能访问基类中的private成员
  • 派生类的写法 class 派生类名:public 基类名 { };

  • 覆盖

    • 派生类可以定义一个和基类成员同名的成员,这叫覆盖
    • 在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员
    • 要在派生类中访问由基类定义的同名成员时,要使用作用域符号::
    • 一般来说,基类和派生类不定义同名成员变量,但有同名的成员函数很常见
  • 派生类对象的内存空间

    • 派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。
    • 在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前

继承实例程序:学籍管理:

#include <iostream>
using namespace std;


class CStudent { // 基类
private:
    string name;    // 名字
    string id;  // id
    char gender;    // 性别
    int age;    // 年龄
public:

    void SetInfo(const string name_, const string id_, int age_, char gender_);
    string GetName();
    void PrintInfo();
};

void CStudent::SetInfo(const string name_, const string id_, int age_, char gender_) {
    name = name_;
    id = id_;
    gender = gender_;
    age = age_;
}

string CStudent::GetName() {
    return name;
}

void CStudent::PrintInfo() {
    cout << "Name: " << name << endl;
    cout << "ID: " << id << endl;
    cout << "Gender: " << gender << endl;
    cout << "Age: " << age << endl;
}

class CUndergraduateStudent : public CStudent { //派生类,继承CStudent类
private:
    string department;
public:
    void SetInfo(const string name_, const string id_, int age_, char gender_, string department_); // 覆盖基类的方法
    void QualifiedForBaoyan(); // 覆盖基类的方法
    void PrintInfo(); // 覆盖基类的方法
};

void CUndergraduateStudent::QualifiedForBaoyan() {
    cout << "qualified for baoyan" << endl;
}

void CUndergraduateStudent::SetInfo(const string name_, const string id_, int age_, char gender_, string department_) {
    CStudent::SetInfo(name_, id_, age_, gender_); // 调用基类的方法
    department= department_;
}

void CUndergraduateStudent::PrintInfo() {
    CStudent::PrintInfo();  // 调用基类的方法
    cout << "Department: " << department << endl;
}

int  main()
{
    CUndergraduateStudent s2;

    s2.SetInfo("Harry Potter ", "118829212", 19, 'M', "Computer Science");
    cout << s2.GetName() << " " ;
    s2.QualifiedForBaoyan ();
    s2.PrintInfo ();

    return 0;
}

输出:

Harry Potter  qualified for baoyan
Name: Harry Potter 
ID: 118829212
Gender: M
Age: 19
Department: Computer Science

2.继承和复合关系

  • 这是继承和复合的核心区别.

image.png

image.png

  • 再来看看复合关系:

image.png

3.派生类覆盖基类成员

image.png

image.png

4.类的保护成员

image.png

  • 相比private成员,给protected成员多了一个权限,即派生类的成员函数可以访问基类的protected成员. 所以,如果想要让派生类的函数可以用基类的一些变量或者函数,那么就用protected.

image.png

image.png

  • 注意了,protected也仅仅是在派生类的成员函数里面可以访问.

5.派生类的构造函数

规范用初始化列表来初始化派生类中包含的那个基类对象,看下面的例子.

image.png

image.png 这个错误的构造函数的错误原因在于,基类的legs和color是private的,父类不能访问.

image.png

  • 在创建派生类的对象时:

    • 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;
    • 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
    • 最后执行派生类自己的构造函数
  • 在派生类对象消亡时:

    • 先执行派生类自己的析构函数
    • 再依次执行各成员对象类的析构函数
    • 最后执行基类的析构函数
  • 析构函数的调用顺序与构造函数的调用顺序相反

6.public继承的赋值兼容规则

image.png

  • 派生类的对象可以赋值给基类对象

    • b = d;
  • 派生类对象可以初始化基类引用

    • base & br = d;
  • 派生类对象的地址可以赋值给基类指针

    • base * pb = & d

7.直接基类和间接基类

image.png

  • 当派生类的对象生成时,会从最顶层的基类开始逐层往下执行所有基类的构造函数,最后再执行自身的构造函数
  • 当派生类的对象消亡时,先执行自身的析构函数,然后再从底向上执行各个基类的析构函数 看一个例子:
#include <iostream>
using namespace std;

class Base { // 基类
protected:
    int n;
public:
    Base(int n_=0) : n(n_) {
        cout << "Base constructed" << endl;
    }
    ~Base(){
        cout << "Base destructed" << endl;
    }
};

class Derived : public Base {
public:
    Derived(int n_) :Base(n_) {
        cout << "Derived constructed" << endl;
    }
    ~Derived(){
        cout << "Derived destructed" << endl;
    }
};

class MoreDerived : public Derived { // 直接基类Derived, 不用声明间接基类Base
public:
    MoreDerived() : Derived(0) {
        cout << "MoreDerived constructed" << endl;
        this->n = 0;
    }
    ~MoreDerived() {
        cout << "MoreDerived destructed" << endl;
    }

};

int  main()
{
    MoreDerived m;

    return 0;
}

输出:

Base constructed       
Derived constructed    
MoreDerived constructed
MoreDerived destructed 
Derived destructed     
Base destructed 

九、多态

1.虚函数与多态的基本概念

image.png

  • 在类的定义中,前面有 virtual 关键字的成员函数就是虚函数
  • virtual关键字只用在类定义里的函数声明中,写函数体时不用
  • 静态成员函数不能是虚函数

image.png

  • 派生类的指针可以赋给基类指针

  • 通过基类指针调用基类和派生类中的同名虚函数时:

    • 若该指针指向一个基类的对象,那么被调用是基类的虚函数
    • 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数

    这种机制就叫做“多态”

image.png

image.png

  • 派生类的对象可以赋给基类引用

  • 通过基类引用调用基类和派生类中的同名虚函数时:

    • 若该引用引用的是一个基类的对象,那么被调用是基类的虚函数
    • 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数

这种机制也叫做“多态”

image.png

看一个例子:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void someVirtualFunction() {
        cout << "Base someVirtualFunction()" << endl;
    }
};

class Derived : public Base {
public:
    virtual void someVirtualFunction() {
        cout << "Derived someVirtualFunction()" << endl;
    }
};

int  main()
{
    Derived d;
    Base *pb, b;

    // 指针
    pb = &d;
    pb->someVirtualFunction(); // 输出“Derived someVirtualFunction()”

    pb = &b;
    pb->someVirtualFunction(); // 输出“Base someVirtualFunction()”

    // 引用
    Base &br1 = d;
    br1.someVirtualFunction();  // 输出“Derived someVirtualFunction()”

    Base &br2 = b;
    br2.someVirtualFunction();  // 输出“Base someVirtualFunction()”

    return 0;
}

2.多态实例:魔法门之英雄无敌

  • 游戏中有很多种怪物,每种怪物都有一个类与之对应,每个怪物就是一个对象, 比如说有Dragon,Wolf, Ghost等
  • 每种怪物都有生命力、攻击力这两种属性
  • 怪物能够互相攻击,攻击敌人和被攻击时都有相应的动作,动作是通过对象的成员函数实现的
  • 游戏版本升级时,要增加新的怪物--雷鸟。如何编程才能使升级时的代码改动和增加量较小?

image.png

image.png

image.png 下面先看一下非多态的实现:

image.png

image.png

  • 需要有N个Attack和FightBack函数,非常麻烦,而且新增一个怪物的时候,要去增加所有怪物类的Attack和FightBack,程序改动较大

下面来看一下多态的实现:

image.png

image.png

image.png

  • 可以看到,只要在每个怪物类里面写一个Attack,Hurted和FightBack即可.

image.png

#include <iostream>
using namespace std;

class Creature {
protected:
    int nLifeValue, nPower;
public:
    virtual void Attack(Creature *p) {}
    virtual void Hurted(int power) {}
    virtual void FightBack(Creature *p) {}
};

class Dragon : public Creature {
public:
    virtual void Attack(Creature *p) {
        p->Hurted(nPower);
        p->FightBack(this);
    }

    virtual void FightBack(Creature *p) {
        p->Hurted(nPower/2);
    }

    virtual void Hurted(int power) {
        nLifeValue -= power;
    }
};

class Wolf : public Creature {
public:
    virtual void Attack(Creature *p) {
        p->Hurted(nPower);
        p->FightBack(this);
    }

    virtual void FightBack(Creature *p) {
        p->Hurted(nPower/2);
    }

    virtual void Hurted(int power) {
        nLifeValue -= power;
    }
};

class Ghost : public Creature {
public:
    virtual void Attack(Creature *p) {
        p->Hurted(nPower);
        p->FightBack(this);
    }

    virtual void FightBack(Creature *p) {
        p->Hurted(nPower/2);
    }

    virtual void Hurted(int power) {
        nLifeValue -= power;
    }
};

int  main()
{
    Dragon dragon;
    Wolf wolf;
    Ghost ghost;

    dragon.Attack(&wolf);
    dragon.Attack(&ghost);

    return 0;
}

3.多态实例:几何形体程序

image.png

image.png

Sample Input: 
3
R 3 5 
C 9 
T 3 4 5

Sample Output
Triangle: 6
Rectangle:15
Circle:254.34

image.png

  • CShape是所有的基类,他的AreaPrintInfo是一个纯虚函数,是没有函数体的,因为一个CShape的对象并没有面积和Info.
  • 派生出的矩形类是CRectangle,多了一个w,h表示宽和高...以及圆CCircle等等.

image.png 这里写了派生类的AreaPrintInfo

CShape *pShapes[100];
intMyCompare(constvoid *s1, constvoid *s2);
intmain()
{
    inti;
    intn;
    CRectangle *pr;
    CCircle *pc;
    CTriangle *pt;
    cin >> n;   // 输出个数
    for (i = 0; i < n; i++)
    {
        char c;
        cin >> c;
        switch (c)
        {
        case 'R':
            pr = new CRectangle();
            cin >> pr->w >> pr->h;
            pShapes[i] = pr;
            break;
        case 'C':
            pc = new CCircle();
            cin >> pc->r;
            pShapes[i] = pc;
            break;
        case 'T':
            pt = new CTriangle();
            cin >> pt->a >> pt->b >> pt->c;
            pShapes[i] = pt;
            break;
        }
    }
    qsort(pShapes, n, sizeof(CShape *), MyCompare);
    for (i = 0; i < n; i++)
        pShapes[i]->PrintInfo();
    return 0;
}

int MyCompare(const void *s1, const void *s2)
{
    double a1, a2;
    CShape **p1; // s1,s2 是void * ,不可写“* s1”来取得s1指向的内容
    CShape **p2;
    p1 = (CShape **)s1; //s1,s2指向pShapes数组中的元素,数组元素的类型是CShape*
    p2 = (CShape **)s2; // 故p1,p2都是指向指针的指针,类型为CShape**
    a1 = (*p1)->Area(); // * p1 的类型是Cshape* ,是基类指针,故此句为多态
    a2 = (*p2)->Area();
    if (a1 < a2)
        return -1;
    else if (a2 < a1)
        return 1;
    elsereturn 0;
}

4.在非构造/析构的成员函数中调用虚函数,是多态

例子:

#include <iostream>
using namespace std;

class Base {
public:
    void fun1() {
        fun2(); // 在非构造函数和非析构函数的成员函数中调用虚函数,是多态
    }

    virtual void fun2() {
        cout << "Base fun2()" << endl;
    }
};

class Derived:public Base {
public:
    void fun2() { // 派生类的虚函数可以不关键字virtual
        cout << "Derived fun2()" << endl;
    }
};

int  main()
{
    Derived d;
    Base *pb = &d;

    pb->fun1();

    return 0;
}

可以看到func1是正常的函数,他调用了虚函数func2,main里面执行func1,会多态地执行func2,所以输出:

Derived fun2()

image.png

在构造函数和析构函数中调用虚函数,不是多态

  • 编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数

如下例:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void Hello() {
        cout << "Base Hello()" << endl;
    }
    virtual void Bye() {
        cout << "Base Bye()" << endl;
    }
};

class Derived:public Base {
public:
    void Hello() {
        cout << "Derived Hello()" << endl;
    }

    void Bye() {
        cout << "Derived Bye()" << endl;
    }

    Derived() {
        Hello();
     }
     ~Derived() {
        Bye();
     }
};

class MoreDerived:public Derived {
public:
    void Hello() {
        cout << "MoreDerived Hello()" << endl;
    }
    void Bye() {
        cout << "MoreDerived Bye()" << endl;
    }
    MoreDerived() {
        Hello();
    }
    ~MoreDerived() {
        Bye();
    }
};

int  main()
{
    MoreDerived md; // 输出:Derived Hello() , MoreDerived Hello()
    cout << endl;

    Base *pb = &md; 
    pb->Hello(); // 多态 输出:MoreDerived Hello()

    cout << endl;

    return 0;
    // 析构 输出:MoreDerived Bye()   , Derived Bye() 
}

5.虚函数的访问权限,取决于基类虚函数的访问权限

image.png

6.虚函数只需要基类+virtual,子类可加可不加

7.多态的实现原理

image.png

  • “多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定 ---- 这叫“动态联编”

image.png

image.png

  • 多态实现的关键 -- 虚函数表

    • 每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针
    • 虚函数表中列出了该类的虚函数地址
    • 多出来的4个字节(64-bit应该是8字节)就是用来放虚函数表的地址的,且放在对象的开头.
    • 多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令

image.png 简单地说,多态的调用函数并不是简单的call一个地址,而是要去虚函数表里面去找那个虚函数地址

image.png 这段程序就是证明了前面的说法,让pa的开头8字节变成a的开头8字节,这样就改变了pa的虚函数表的地址,那么调用Func的时候,就去调用A的func

8.虚析构函数

虚析构函数是推荐做法! 问题:

  • 如果不给基类做一个虚析构函数的话,那么通过基类的指针去delete派生类对象时,通常只会调用基类的析构函数. 解决办法:
  • 把基类的析构函数声明为virtual

image.png

  • 通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数

    • 但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。
  • 解决办法:把基类的析构函数声明为virtual

    • 派生类的析构函数可以virtual不进行声明
    • 通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数
  • 一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。

  • 注意:不允许以虚函数作为构造函数

image.png

image.png

9.纯虚函数和抽象类

image.png

class A {
private: 
  int a;
public:
  virtual void Print( ) = 0 ; //纯虚函数
};

image.png

  • 包含纯虚函数的类叫抽象类

    • 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
    • 抽象类的指针和引用可以指向由抽象类派生出来的类的对象

image.png

  • 之所以抽象类的成员函数可以调用纯虚函数,是因为基类的成员函数里调用虚函数是多态的形式。
  • 之所以构造/析构函数不能调用纯虚函数,因为此时不是多态,所以不能调用一个函数体都没有的函数.
  • 继承自抽象类,必须实现所有纯虚函数才不是抽象类,这个和Java很像.

image.png

十、输入输出和文件操作

1.输入输出流相关的类

image.png

image.png

  • ios是抽象的基类,派生出istream和ostream
  • istream是用于输入的流类,cin就是该类的对象
  • ostream是用于输出的流类,cout就是该类的对象
  • ifstream是用于从文件读取数据的类
  • ofstream是用于向文件写入数据的类
  • iostream是既能用于输入,又能用于输出的类
  • fstream 是既能从文件读取数据,又能向文件写入数据的类

image.png

image.png

  • cin对应于标准输入流,用于从键盘读取数据,也可以被重定向为从文件中读取数据
  • cout对应于标准输出流,用于向屏幕输出数据,也可以被重定向为向文件写入数据
  • cerr对应于标准错误输出流,用于向屏幕输出出错信息
  • clog对应于标准错误输出流,用于向屏幕输出出错信息
  • cerr和clog的区别在于cerr不使用缓冲区,直接向显示器输出信息;而输出到clog中的信息先会被存放在缓冲区,缓冲区满或者刷新时才输出到

image.png 如果是从文件输入的话,那么读到尾部算结束。如果是键盘输入,接收到Ctrl+Z算结束。

还有一个比较难的点,就是虽然operator>>重载的返回值是istream &,不应该拿来做while的条件,但是istream是做了强制转换运算符的重载,给重载成了bool类型.

代码示例如下:

#include "bits/stdc++.h"
using namespace std;
int main(){
    int x;
    while (cin >> x){
        cout << "echo : " << x << endl;
    }
    system("pause");
}

输出如下:

PS D:\leetcode> g++ a.cpp ; ./a
1 2 3 4
echo : 1
echo : 2
echo : 3
echo : 4
^D
请按任意键继续. . . 

image.png

istream& getline(char * buf, int bufSize)

这个getline是以\n为分隔符

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

这个getline以delim为分隔符,不以\n为分割符

  • 这个getline很棒的一点是,这个bufSize是包括了结尾的\0在内的,所以char数组有多大,这个bufSize就有多大. 如果达到或超过了
  • getline在读到了结尾,或者超过了bufSize时返回0.

image.png

cin.eof();//判断输入流是否结束
cin.peek();//读输入流的下一个字符,但不会从流中去掉(瞥一眼)
cin.pushback();//字符放到输入流
cin.ignore(int nCount=1,int delim=EOF);//输入流中最多删掉nCount个字符,或者遇到EOF了就结束

image.png


freopen来做输入/输出重定向.

image.png

  • 在这里,标准输出的输出到test.txt中,标准错误输出是输出到终端中 image.png
  • 标准输入重定向到了一个文件.

十一、模板

1.函数模板

image.png

image.png

image.png

image.png

image.png

image.png