C++primer——阅读笔记

1、文件重定向

$ a.out <infile >outfile 

2、Unicode编码

3、内置类型的机器实现(解决转换问题)

P31

4、引用

对于引用的操作,实际上是作用在引用所引的对象上。,使用引用作为参数有两个用处:1)避免拷贝 2)返回额外的值
1、P45

2、指向指针的引用 P52
引用本身不是一个对象。

    int i = 42;
    int *p;
    int *&r = p;

    r = &i;
    *r = 0;   //最终i被设置成了0

3、对常量的引用 P54

    const int ci = 1024;
    const int &r = ci;

    int  i = 42;
    const int &r1 = i;  //允许const int&绑定到一个普通int对象上

4、const和指针 P56

    const int i = 42;
    int *p = &i;  //错误
    const int *cp = &i;

    int a = 43;
    cp = &a;  //正确,但是不能通过*cp = 0;这种操作来改变a的值。因为const int *cp 所指向的内容不能变。

5、指针类型别名

    typedef char *pstring;

    const pstring p = 0;   //指向char的【常量指针】
    const char * p = 0;    //指向const char的【指针】,即指向常量字符的指针

6、数组引用

    int &a[10] = ?  //不存在引用的数组,因为数组的元素应该为对象,引用不是对象。
    int (&ar)[10] = arr;  //正确,P102 表示ar是一个引用,它【引用的对象】是一个大小为10的数组,数组中元素的类型是int

    int arr[10];
    int (*p)[10] = &arr;  //【指向数组】的【指针】

7、设置形参为引用 P189

    bool isShorter(const string &s1, const string &s2)  //把形参定义为【对常量】的引用
    {
        return s1.size() < s2.size();
    }

8、尽量使用常量引用
P192

    find_char(string &s, char c,...)     //s只能接受普通引用
    find_char("hello world", '0', ...);  //这样的调用是错误的

9、不要返回局部对象的引用或指针 P201
要想确保返回值安全,我们应该确定:引用所引的是在函数之前已经存在的哪个对象?
10、引用返回左值
P202

    char & get_value(string &s, int ix);
    get_value(s,0) = 'A';   //将s[0]的值改为A
    //如果返回的是常量引用,我们不能给其赋值。

范围for循环

vector<int> v = {0,1,2,3,4};

//&r代表会改变v中的数据,用引用
for(auto &r : v)
{
    r *= 2;
}

5、size_type

1、size_type

    string::size_type

    vector::size_type  //错误
    vector<int>::size_type

2、difference_type
带符号的整数型
3、数组下标
size_t (cstddef头文件)

6、迭代器

迭代器类型定义:

    vector<int>::iterator it;   
    string::const_iterator is;

【谨记】但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

7、数组

1、

    char a[] = "c++";   //维度为4,后面自动添加空字符
    char a1[] = {'c','+','+'};  //维度为3,没有空字符,也不会自动添加

    int * beg = begin(a);
    int * end = end(a);  //指向数组a的尾后元素 包含iterator头文件

2、使用数组初始化vector对象

    int arr[] = {1,2,3,4,5,6};
    vector<int> vec(begin(arr), end(arr));

    vector<int> vec2(arr+1, arr+4);  //拷贝了2,3,4给vec2

3、指针和多维数组
P115
4、数组别名
P205

    typedef int arrT[10];   //arrT是一个类型别名,它表示的类型是:含有10个整数的数组。
    using arrT = int[10];

    arrT * func(int i);     //返回一个指向包含10个整数的数组的指针。

5、返回数组指针的函数
P205

    int (* p)[10]; //p是一个指针,指向一个包含10个整数的数组。 int (* func(int i))[10]; //返回【数组指针】的函数 auto func(int i)->int (*)[10];  //返回数组指针

8、表达式

    if(val == true)  //只有当val等于1时才为真,最好不用使用布尔字面值(true/false)进行比较
    if(val)

9、类型转换

P145
const_cast: 去掉const性质,只适用于底层const

    const char *c_p;  //底层const,即所指的内容为常量
    char *p = const_cast<char *>(c_p);  //通过p写值是未定义的。
              const_cast<int *>(c_p);   //错误

10、异常

    throw runtime_error("*****");

11、重载

P208
1、顶层const( 指针本身为常量)无法实现重载,即本身为一个常量。
而底层const可以实现重载,因为指针、引用所指向、引用的值为一个常量,在调用的时候可以通过判断是否是常量来选择。

    int lookup(phone);
    int lookup(const phone);   //无法重载

    int lookup(phone *);
    int lookup(phone * const);  //无法重载

    int lookup(const phone *);  //可以重载

2、const_cast和重载

    const_cast<const string&>(s);  //将普通的string &转换成常量引用
    const_cast<string &>(s);       //将常量引用转换为普通引用

12、默认实参

P212
局部变量不能作为默认实参。

13、函数匹配

    void ff(int);
    void ff(short);
    ff('a');    //char提升为int,调用ff(int)

    void ff(long);
    void ff(float);
    ff(3.14);  //二义性,3.14是double类型,既能转换为long也可以转换为float

14、函数指针

1、函数指针形参

    //函数指针形参
    void fun(const string &s, bool pf(int,int));     //pf为函数类型,自动转换为指向函数的指针
    void fun(const string &s, bool (*pf)(int,int));  //pf为指针类型

2、返回指向函数的指针
P223

    typedef int (*pf)(int *, int);  //pf是函数指针,指针类型

    pf f1(int);                  //①f1是一个函数,返回指向函数的指针
    int (*f1(int))(int *, int);  //②与上面的等价
                                 //f1是一个函数,返回一个指针,该指针是一个【函数指针】,指向一个函数(返回值为int,参数为int *, int的函数)

    //尾置返回类型
    auto f1(int)->int (*)(int *, int);  //③

15、this指针

1、this是一个指向调用函数的对象本身,是一个常量指针,该指针的值不可改变。

    A a;  //类A,定义对象a
    a.fun();   //当a调用成员函数时,this指向了a本身。

2、返回*this的成员函数
P247

    &Screen move()
    {  ...; return *this;}  //返回的是调用该接口的对象的引用,注意返回的是&Screen,而不是Screen。
    &Screen set()
    {  ...; return *this;}  
    //那么我们这样调用
    myScreen.move().set();  //相当于myScreen.move() myScreen.set()

    const &Screen display() const
    { ...; return *this;}   //this是一个指向常量的指针,而*this将是一个常量对象。
    myScreen.display().set();  //display返回【常量引用】,调用set时将会出错,无法改变常量引用的值。

16、可变数据成员 mutable

17、类 类型成员

P246

    class Window_mgr
    {
    private:
        std::vector<Screen> s{Screen(24,80,'')};  //容器vector里装的是Screen类,Screen(24,80,'')只是初始了vector中的第一个块。
    }

18、友元 为了访问类中的私有成员

P241
友元关系不能继承。
友元与继承 P545

class Base
{
public:
    void pub_men(); 
    friend class Pal;    //声明Pal类为友元
protected:
    int prot_mem;
private:
    char priv_mem;
};

class Sneaky : public Base
{
    //派生类能访问保护成员,不能访问私有成员
    friend void clobber(Sneaky &);  //能访问Sneaky::prot_men P543
    friend void clobber(Base &);    //不能访问Base::prot_mem
    int j;     //j is private
};

class Sneaky2 : private Base
{
    //派生类能访问保护成员,不能访问私有成员
    //Sneaky2对Base是私有继承,因此在默认情况下继承来的成员都是Sneaky的私有成员。
    //使用using声明改变访问属性 P546
};

class Pal
{
public:
    int f1(Base b) {return b.prot_mem};   //Pal是Base的友元,可以访问其私有成员
    int f2(Sneaky s) {return s.j};        //Pal不是Sneaky的友元,不能访问其私有成员
    int f3(Sneaky s) {return s.prot_mem}; //Pal是Base的友元,可以访问派生类中基类的私有成员 
};

Sneaky s1;  //继承Base是public
Sneaky2 s2;
s1.pub_mem;  //ok
s2.pub_mem;  //pub_mem在派生类中是私有的了

19、构造函数

1、构造函数初始值列表(必须用此方法的情况) P258
如果成员const或者引用时,或者属于某种类类型且该类没有定义默认构造函数时,必须通过构造函数初始值列表为这些成员提供初始值。

    class test{
    public:
        test(int a);
    private:
        int i;
        const int ci;
        int &ri;
    };

    //错误的构造函数
    test::test(int a)
    {
        i = a;
        ci = a;  //错误,不能给常量赋值
        ri = a;  //错误,ri还未初始化呢,都不知道指向哪个鬼东西~
    }
    //正确的做法为使用:构造函数初始值列表
    test::test(int a):i(a),ci(a),ri(a)
    {   

    }

2、默认构造函数的作用 P262 ????
3、显示构造类的对象

    item.combine(Sales_data(null_book));  //Sales_data(null_book)构造了一个Sales_data类的对象

20、静态成员

P269
静态成员存在于任何对象之外
static关键字只出现在类内部的声明语句中。P270
静态成员不能在类内部定义和初始化,必须在外部(类似于全局变量)。

    double Account::rate = ...;

静态成员可以是不完全类型,可以是它所属的类的类型,而非静态成员只能是所属类类型的指针或引用。
静态成员可以作为默认实参,非静态成员因为其值本身属于对象的一部分,不能作为默认实参。

21、容器适配器

P329
每个适配器都定义两个构造函数,A a or A a(c)

22、string 对象

P338

    string sum = accumulate(v.cbegin(), v.cend(), string(""));  //string上定义了+号运算,即链接字符串
    string sum = accumulate(v.cbegin(), v.cend(), "");  //错误,const char *没有定义+号运算

    str.substr(pos,n)  //P322 拷贝POS位置开始的n个字符,并返回该子字符串string

23、back_inserter

P341
back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此插入迭代器赋值时,赋值运算符会调用push _back将一个具有给定值元素添加到容器中。

24、容器适配器

P329
所有适配器都需要有添加删除的能力,因此适配器不能构造在array之上,其是固定大小的。
stack、queue是默认基于deque实现的,priority_queue是基于vector实现的。
s.pop
s.push(item)
s.emplace(args)
s.top()

    stack<int> intStack;
    for(size_t ix=0; ix != 10; ++ix)
    {
        intStack.push(ix);
    }

25、lambda

lambda表达式:(p346)

[capture list] (parameter list) -> return type { function body} // capture list 是从lambda所在函数中传递过来的局部变量(非static)列表 
void biggies(vector<string> &words, vector<string>::size_type sz)
{
    elimDups(words);

    auto wc = find_if(words.begin(), words.end(), [sz](const string &a){ return a.size() >= sz; });
    //++ 从words起始到终点,找到一个大于等于sz的words
}

lambda值捕获和引用捕获的区别(P350),应该尽量避免捕获指针或引用。而ostream对象不能被拷贝,此时只能使用引用捕获。

可变lambda

size_t v1 = 42;
auto f = [v1] () mutable { return ++v1; };
v1 = 0;
auto j = f();   // j为43

26、explicit构造函数只能用于直接初始化

例如tuple类型的初始化

tuple<size_t, size_t, size_t> a = {1,2,3};  // 错误
tuple<size_t, size_t, size_t> a{1,2,3};  // 正确,直接初始化

27、decltype()返回一个对象的类型

28、输出缓冲区

程序结束、缓冲区满、使用了操纵符如endl、设置unitbuf、输出流关联到了另外一个流(该流读写时)的时候都会导致输出缓冲区被刷新。cin和cerr都关联到了cout,那么读cin或写cerr时,都会导致cout的缓冲区被刷新,而cerr是立即刷新的。

cout<<unitbuf;    //所有输出操作都会立即刷新缓冲区
//do somethings

cout<<nounitbuf;  //恢复默认

动态绑定(运行时绑定)

当我们使用基类的引用(指针)调用一个虚函数时,将发生动态绑定,运行的时候根据传入的实参决定运行的版本,是调用基类的还是派生类的。

不存在从基类向派生类的隐式类型转换

P530
因为派生类对象中含有基类对应的部分,所有我们能把派生类的对象当成基类对象来使用,
也可以将基类的指针或引用绑定到派生类对象的基类部分。
当给基类的构造函数传递一个派生类对象时,实际运行的构造函数是基类中定义的那个,只能出来基类自己的成员。
将一个派生类对象赋值给一个基类对象时,实际运行的赋值运算符也是基类中定义的那个。
当我们用一个派生类对象为一个基类对象初始化、赋值时,只有派生类对象中的基类部分会被拷贝、移动或者赋值,它的派生类部分将被忽略掉。
P536
派生类向基类的类型转换只对指针、引用有效。
基类向派生类不存在隐式类型转换。

虚函数

任何构造函数之外的非静态函数,都可以是虚函数。
动态绑定

    double print_total(ostream &os, const Quote &item, size_t n)    //使用基类的引用
    {
        //根据传入item形参的对象类型调用Quote::net_price(基类) or Derive_quote::net_price(派生类)
        double ret = item.net_price(n);
        double ret = Quote::item.net_price(n);  //作用运算符,强行回避虚函数机制
    }

如果虚函数包括默认实参,那么默认实参值由本次调用的静态类型决定。P539,通过基类的引用指针调用函数,则使用基类的默认实参,而不会在实际运行中选择派生类的。

我们不能创建抽象基类的对象,可以定义其派生类的对象,前提是其派生类中对基类的虚函数进行了覆盖。

多态性

引用和指针的静态类型和动态类型不同这一事实是C++语言支持多态性的根本。

继承

防止继承的发生:final P533

派生访问说明符 可访问性

P544
派生访问说明符的目的:控制派生类用户(包括派生类的派生类)对于基类成员的访问权限。

class Base
{
public:
    void pub_men();   
protected:
    int prot_mem;
private:
    char priv_mem;
};

class Sneaky : public Base    //派生访问说明符位public
{
    //派生类能访问保护成员,不能访问私有成员
};

class Sneaky2 : private Base
{
    //派生类能访问保护成员,不能访问私有成员
};

Sneaky s1;  //继承Base是public
Sneaky2 s2;
s1.pub_mem;  //ok
s2.pub_mem;  //pub_mem在派生类中是私有的了

P545 类的设计和受保护的成员
基类应该将其接口声明为公有的,将其实现分成两组:一组供派生类访问,保护的;一组只能由基类及其友元访问,为私有的。

派生类将隐藏基类同名成员(函数)

P549

class Base{
    int memfun();
};

class Derived : Base{
    int memfun(int);
};
Base b;
Derived d;
d.memfun();  //错误,参数列表为空的memfun被隐藏了
d.Base::memfun();

模板&泛型编程

inline/constexpr必须在中间、函数参数是const的引用(使得函数可用于不能拷贝的类型大对象) P581

    template <typename T, class U> inline/constexpr T min(const T &v1, const U &v2);

    //类外部的成员函数
    template <typename T>
    ret-type Blob<T>::fun(...);

    //在一个类模板作用域内,我们可以直接使用模板名(Blob),而不必指定模板实参(Blob< T >)。
    template <typename T> Blob<T> Blob<T>::fun(...)
    {
        Blob ret = *this;   //直接使用模板名
    }

在一个类模板作用域内,我们可以直接使用模板名(Blob),而不必指定模板实参(Blob< T >)。

拷贝、移动构造函数/运算符

拷贝(赋值)构造函数的第一个参数必须是自身类型的引用,且其他额外参数必须有默认值。 P440

    Foo& operator=(const Foo&)
    {
        //初始化运算、赋值
        return *this;  //返回左侧运算符对象的引用。
    }

识记点

IO 类(istream ostream)不能被拷贝,因此只能通过引用来传递。P234
构造函数不能被声明为const。P235
如果成员是const或者引用时,或者属于某种类类型且该类没有定义默认构造函数时,必须初始化。P258
类成员的初始化顺序与其在类定义中的顺序一致。
如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。P260
如果定义了其他构造函数,最好也提供一个默认构造函数。
非类型模板参数的模板实参必须是常量表达式。P581
拷贝(赋值)构造函数的第一个参数必须是自身类型的引用,且其他额外参数必须有默认值。 P440
虚函数在构造函数中,已经失去了虚函数的动态绑定特性。
不能用同时用static和const(放在函数参数列表后的修饰)修饰类的成员函数。P269
static公有成员,因为它们独立于class对象之外,我们不必产生对象也可以使用它们。

奇异方法

    int *p(new int(42));  //p指向动态内存

    int *p = new int[42];
    typedef int arrT[42];  //数组类型别名,arrT表示42个int型的数组类型,是类型。
    int *p = new arrT;     //分配一个42个int的数组,p指向第一个int

    int *p = new int[42]();  //42个0的数组

    delete [] p;
    vector<int> ivec;
    for(vector<int>::size_type i=0; i!=10; ++i)
    {
        ivec.push_back(i);
    }
    set<int> iset(ivec.cbegin(),ivec.cend());

    set<int>::iterator it_set = iset.begin();

    map<string,int> imap;  //imap[key]表示值(value) key->value
    auto it_map = imap.cbegin();  //it_map是指向一个pair<const string,int>对象的引用
    it_map->first;
    it_map->second;

    ifstream &map_file;  //P393
    map_file >> key;
    getline(map_file, value);
    //文件每一行是这样的:word other stirngs
    //那么key == word value将得到word之后的内容,包括word与other之间的空格
    value = value.substr(1);   //去掉word与other之间的空格
    struct PersonInfo
    {
        string name;
        vector<string> phone;
    };

    vector<PersonInfo> people;

    PersonInfo info;
    info.name ...
    info.phone.push_back(...);

    people.push_back(info);

你可能感兴趣的:(C++)