C++ primer 笔记

来源:互联网 发布:php网站访问量统计代码 编辑:程序博客网 时间:2024/06/05 21:14

1. int month = 9, day = 7; // 这样是无错的

2. 非const变量默认是extern。可以被其他文件访问,const变量默认是非extern的,也就是说在其他文件中不能被访问,要使const变量能够在其他的文件中访问,必须显示的指定它为extern

3. 

  1 #include <iostream>  2   3 using namespace std;  4   5 class A {  6 public:  7     A(){  8         cout << "A construct" << endl;  9     } 10 }; 11  12 class B { 13 public: 14     B() { 15         cout << "B construct" << endl; 16     } 17 private: 18     A a; 19 }; 20  21 int main(void) 22 { 23     B b; 24     return 0; 25 }// 输出为:A constructB construct
4. int& i = 42; // 错误

   const int& i = 42; // 正确

解释:第一个之所以错误是因为42是一个字面值,它是一定不能被改变的,而非const的是可以改变其值的,但字面值不允许改变,逻辑上不合理,所以出错,而第二个合理。类似的问题有:

int i = 42;

const int &r = 42; // 合理,已经讨论过

const int &r2 = r + i;

编译器会将第3个转换成如下代码:

int tmp = r + i;

const int &ri = tmp;

这样就避免了被修改的问题

注:非const引用只能绑定到与该引用同类型的对象

const引用则可以绑定到不同但相关的类型的对象(可隐身转换)或绑定到右值

5. enum可以这样使用

enum Pionts{    point2d = 2,    point2w,    point3d = 3,    point3w,};int main(void) {    Points pt3d = point3d; // ok, because point3d is a Points enumerator    return 0;}
6. 如果const变量不是用常量表达式初始化,它就不应该在头文件中定义

const double pi = 3.1416; // 常量表达式初始化,应该放在头文件中定义

const double sq2 = sqrt(2.0); // 非常理表达式初始化,不应该放在头文件中

注:当该const变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式来替换对这些const变量的使用。所以在实践中,不会有任何存储空间用于存储常量表达式初始化的const变量

7. 任何存储string的size操作结果的变量必须为string::size_type类型。特别重要的是,不要把size的返回值赋值给一个int变量

注:就算不知道string::size_type的确切类型,但可以知道它是unsigned,而int是signed,而且在一些机器上int表示的范围太小只有32767个字符

8. string s1 = "hello" + ", "; // error: no string operand

   string s2 = "hello" + ", " + s2; // error: can't add string literals

9. string s; cout << s[5] << endl; // 这样是不会抛错的,运行时可能会出大麻烦,一定要检查

10. vector不是一种数据类型,而是一个类模板,可用来定义任意多种数据类型。vector类型的每一种都指定了其保存元素的类型。因此vector<int>,vector<string>都是数据类型

11. 使用size_type类型时,必须指出改类型是在哪里定义的。vector类型总是包括vector的元素类型:

vector<int>::size_type  // ok

vector::size_type // error

12. 两个指针减法操作的结果是标准库类型ptrdiff_t的数据,在cstddef头文件中定义。size_t是unsigned类型,而ptrdiff_t是signed整形。

13. 指针和typedef

typedef string *pstring;const pstring cstr;// 很多人认为真正的类型是:const string *cstr; // wrong// const修饰的是pstring的类型,这是一个指针string *const cstr; // 这才是对的
14. 下列哪些初始化是合法的?
int i = -1; // okconst int ic = i; // ok, 定义了一个int型const对象ic,并用int型对象对其进行初始化const int*pic = &ic; // ok, 定义了一个指向int型const对象的指针pic,并用ic的地址对其进行初始化int *const cpi = &ic;⁣ // cpi是一个指向int型对象的const指针,不能用const int型对象ic的地址对其进行初始化const int *const cpic = &ic;⁣ // 定义了一个指向int型const对象的const指针cpic,并用ic的地址对其进行初始化
上边这个问题主要看的不是左边,应该是右边,以右边为主就对了

15. C++在new时的初始化的规律可能为:对于有构造函数的类,不论有没有括号,都用构造函数初始化;如果没有构造函数,则不加括号的new只分配内存空间,不进行初始化,而加了括号的new会在分配内存的同时初始化为0。

int *pia = new int[10];// uninitialint *pia2= new int[10](); // initial with 0

16. 对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。

17. 初始化

// error:uninitialized const arrayconst int *pci_bad = new const int[100];// ok: value-initialized const arrayconst int *pci_ok = new const int[100]();// ok:array of 100 empty stringsconst string *pcs = new const string[100];// 详见15
18. 定义指向数组的指针。

int ia[3][4]; // array of size 3, each element is an array of ints of size 4int (*ip)[4] = ia; // ip points to an array of 4 intsip = &ia[2]; // 给ip赋值int *ip[4]; // ip是一个4维的指针数组
19. 对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整数操作数
20. 移位优先级:比算数操作符低,比关系操作符,赋值操作符和条件操作符优先级高

cout << 42 + 10; // okcout << (10 < 42); // ok, print 1(true)cout << 10 < 42; // error, = (cout << 10) < 42,cout 不能与42做比较
21. 
int i, j, ival;const int ci = i; // ok, 这是初始化不是赋值ci = ival; // 不能赋值
22. 建议:只有在必要时才使用后置操作符

因为前置操作需要做的工作更少,只需加1返回加1的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。

23. sizeof操作符

#include <iostream>using namespace std;int main(void) {    int a = 0;    cout << sizeof(a++) << endl;  // 0    cout << a << endl;    cout << sizeof(++a) << endl;  // 0}// 因为sizeof是一个操作符,所以不执行表达式
24. 
const int *pci = new const int; // error, no initailized
25. 对于switch结构,只能在它的最后一个case标号或default标号后面定义变量:

int main(void){    int a = 5;    switch(a) {    case 1:        string file_name = "foo"; // error,不是最后一个case或default, 这是为了避免代码跳过变量的定义和初始化,goto同义        break;    case 2:        break;    };      return 0;}
26. 预处理器在调试时非常有用的常理:

__FILE__ 文件名

__LINE__ 当前行号

__TIME__文件被编译的时间

__DATE__文件被编译的日期
27. 应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不太灵活。这样的形参既不能用const对象初始化,也不能用字面值或产生右值的表达式实参初始化。

int incr(int &val) {    return ++val;}int main(void){    short v1 = 0;    const int v2 = 42;    int v3 = incr(v1);  // error: v1 is not an int    v3 = incr(v2);      // error: v2 is const    v3 = incr(0);       // error: 0 is literals    incr(v1 + v2);      // error: v1+v2 is lvalue    return 0;}
28. 指向指针的引用

int *&v1; 
29. 数组形参:当编译器检查数组形参关联的实参时,它只会检查实参是不是指针,指针的类型和数组元素的类型是否匹配,而不会检查数组的长度。所以将数组形参直接定义为指针比较好

void printValues(int*) {}void printValues(int[]){}void printValues(int[10]){}// 上面3种定义等价
30. 通过引用传递数组

如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身,在这种情况下,数组大小成为形参和实参类型的一部分。

void printValues(int (&arr)[10]) {}int main() {    int i = 0, j[2] = {0,1};    int k[10] = {0,1,2,3,4,5,6,7,8,9};    printValues(&i); // error    printValues(j);   // error    printValues(k);  // ok}
31. 多维数组的传递

void printValues(int matrix[][10]) {}// 数组中的每个元素本身就是含有10个int型对象的数组
32. const成员函数

在函数声明形参表后面的const所起的作用:const改变了隐含的this形参的类型。即为在声明为const的函数内部只能“读取”而不能修改它们的对象的数据成员。

const对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数,则是错误的。
33. 构造函数的初始化列表,具有类类型的成员皆被其默认构造函数自动初始化
34. 重载与作用域

void print(const string&);void print(double);void fooBar(int ival) {    void print(int);    print("Value:"); // error:print(const string&) is hidden    print(ival); // ok: print(int) is visible    print(3.14); // ok: calls print(int); print(double) is hidden}
注解:在C++中,名字查找发生在类型检查之前。
35. 以下不合法的

    int calc(int, int);    int calc(const int, const int); // declaration is ok, but redefinition        void print(int&);    void print(const int&); // OK    int get();    double get();// error:redeclaration    int *reset(int*);    double *reset(double*); // ok

注:重载只检查 名+参数列表   不检查返回类型。const在函数中的含义是“该值在此函数范围内无法修改”。站在调用者的角度,所有的值传递都无法修改实参的。所以第一对在调用者看来都是相同的,不能构成重载。第二对const int&所指向的对象是无法修改了,语义不同,构成重载。
36. 返回指向函数的指针

int (*ff(int)) (int*, int);
注:阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解。

首先观察:ff(int), 将ff声明为一个函数,它带有一个int型的形参。该函数返回 int(*)(int*,int)

使用typedef可使该定义更简明易懂:

typedef int (*PF)(int*, int);PF ff(int);
但是注意:

typedef int func(int*, int);func f1(int); // error, 不能直接返回一个函数,只能返回指针func *f3(int);// ok, 返回的是一个函数指针

37. 判断流是否正常

if (cin)    // ok to use cinwhile(cin >> word)    // ok: read operation successful....
38. 容器的初始化
int main(void){    const char *words[] = {"stately", "plump", "buck", "mulligan"};    size_t words_size = sizeof(words)/sizeof(char*);    vector<string> svec(words, words + words_size);    list<string> slist(svec.begin(), svec.end());    vector<string>::iterator mid = svec.begin() + svec.size()/2;        deque<string> front(svec.begin(), mid);    deque<string> back(mid, svec.end());    return 0;}
39. 容器内元素的类型约束

C++语言中,大多数类型都可用做容器的元素类型。容器元素类型必须满足以下2个约束:

元素类型必须支持赋值运算

元素类型的对象必须可以复制

40. 迭代器相关

// 常用迭代器运算*iter;   // 返回迭代器iter所指向的元素的引用iter->mem; // 对iter进行解引用,获取指定元素中名为mem的成员。等价于(*iter).mem++iter; // 给iter加1,使其指向容器中的下一个元素iter++;--iter; // 给iter减1,使其指向容器里的前一个元素iter--;iter1 == iter2;iter1 != iter2;

// vector和deque类型迭代器支持的操作iter+n; // 指向迭代器n个iter-n;iter1+=iter2;iter1-=iter2;iter1-iter2;>, >=, <, <=
41. c1.swap(c2); // 交换内容,调用后,c1存放的是c2的元素,c2中存放的c1的元素。该函数的执行速度通常比将c2的元素复制到c1的操作快

注:要交换的容器的类型必须匹配:操作数必须是相同类型的容器,而且所存储的元素类型也必须相同

42. 如果两个容器类型相同,其元素类型也相同,assign与等号(=)等价

如果在不同(或相同)类型的容器中,元素类型不相同但是相互兼容,则其赋值运算必须使用assign函数

vector<char*> cvec;....list<string> slist;slist.assign(cvec.begin(), cvec.end());
43. string的拓展

除了一些特殊操作,可将string类型视为字符容器。string类型提供与vector容器相同的操作。

string类型与vector类型不同的是,它不提供以栈方式操作容器:在string类型中不能使用front, back和pop_back操作

44. 默认的stack(栈)和queue(FIFO)都基于deque(双端队列:内存连续)容器实现,而priority_queue则在vector容器上实现。

在创建适配器时,通过将一个顺序容器指定为适配器的第二个类型实参,可覆盖其关联的基础容器类型。基础容器可以是任意一种顺序容器类型。

stack可以建立在vector,list,或者deque容器上

queue要求关联容器必须提供push_front运算,因此只能建立在list上,而不能vector

priority_queue要求提供随机访问功能,因此可以建立在vector或deque容器上,不能再list容器上
45. map下标操作符的使用

cout<< word_count["Anna"]; // print 1++word_count["Anna"];cout<< word_count["Anna"]; // print 2
46. map的查找操作
// 方法1. find函数int occurs = 0;map<string,int>::iterator it = word_count.find("foo_bar");if (it != word_count.end())    occurs = it->second;// 方法2. count函数int occurs = 0;if (word_count.count("foobar"))    occurs = word_count["foobar"];
47. map容器的erase操作返回void,顺序容器的erase操作返回一个指向被删除元素后面的迭代器

区别例子详见:这里

48. multimap:用来一键对多值

m.lower_bound(k);    // 返回一个迭代器,指向键不小于k的第一个元素

m.upper_bound(k);   // 返回一个迭代器,指向键不大于k的第一个元素

m.equal_range(k);    // 返回一个迭代器的pair对象,它的first等价于lower_bound,它的second等价于upper_bound

49. 泛型算法

iter = find(iter_begin, iter_end, value); // 从iter_begin到iter_end范围内找到value并返回iter, 也适用于数组count(iter_begin, iter_end, value); // 从iter_begin到iter_end范围内统计value的次数sum = accumulate(iter_begin, iter_end, init_value); // 累加从iter_begin到iter_end范围内的值,注意:init_value的类型必须同模板类型一致fill(iter_begin, iter_end, value); // 用value填充fill_n(back_inserter(vec), 10, 0); // 相当于vec.push_back(0), 调10次copy(ilst.begin(), ilst.end(), back_inserter(ivec)); // 效果较差,相当于vector<int> ivec(ilst.begin(), ilst.end());replace(ilst.begin(), ilst.end(), 0, 42); // 替换所有是0的值为42end_unique = unique(iter_begin, iter_end); // 结果先排序,把不重复的数据赋值到前段,然后返回最后一个不重复的数据的下一个iter,不支持listsort(iter_begin, iter_end, cmp); // 排序stable_sort(iter_begin, iter_end, cmp); // 相同的元素,相对位置保持不变count_if(iter_begin, iter_end, con_func);  // 统计符合con_func条件的个数

50. 声明一个类:
class Screen;
在声明之后,定义之前,类Screen是一个不完全类型,即已知Screen是一个类,但不知道包含哪些成员。
不完全类型只能以有限的方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。

类不能具有自身类型的数据成员。类的数据成员可以是指向自身类型的指针或引用

51. 为什么要加“;”分号

class Sales_item{/* ... */};

class Sales_item{/* ... */}accum, trans; // 这样是可以的,但是这样很二

52. 可变的数据成员

mutable size_t access_ctr; // may change in a const members   表示即使在const函数都可以改变access_ctr的值

C++的设计真是大奇葩,如果需要用mutable的东西明显是说明你的设计不合理,它提供的mutable就是让你在出现设计问题的时候用一个破坏设计的东西来避免错误,到最后就累计错误,到无法挽回

53. 关于类型名

typedef double Money;class Account {public:    Money balance(){return bal;};private:    typedef long double Money; // error,一旦一个名字被用作类型名,该名字就不能重复定义    Money bal;};
54. 构造函数不能声明为const,因为没有意义
55. 构造函数初始化式

1. const对象或引用类型对象必须在初始化列表中初始化,没有默认构造函数的类成员,要用初始化列表

2. 成员初始化的次序:定义成员的次序,而不是初始化列表的次序,所以有可能会犯的错误,如下!

class X {int i;int j;public:    X(int val):j(val), i(j) {}};// 首先是i先被初始化,其次是j,上边的例子,用未初始化的j初始化i,很可怕的错误!
所以按照与成员声明一致的次序编写构造函数初始化列表是个好主意。
56. 使用explicit可以防止隐式转换
class Foo {public:    explicit Foo(const string& str) {}};int testFoo(const Foo& foo) {    return 0;}int main(void){    string s;    testFoo(s); // error, s没办法隐式转换成foo,因为foo的构造函数声明为explicit    return 0;}

注:explicit只能用于类内部的构造函数声明上。在类的定义体外部所做的定义不再重复它

57. 对于“没有定义构造函数”并且“全体数据成员均为public”的类,可以采取用与初始化数组元素相同的方式初始化其成员

struct Data {int ival; char *ptr;};Data val1 = {0,0}; // OKpair<int, int> p2 = {0,42}; // error, 虽然成员都是public,但它定义了构造函数
58. static数据成员

static数据成员必须在类定义体的外部定义,应该在定义时进行初始化

这个规则的一个例外是,是要初始化式是一个常量表达式,整形const static数据成员就可以在类的定义体中进行初始化。

const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义

在类内部提供初始化式时,成员的定义不必再指定初始值。
59. 复制构造函数:

Sales_item::Sales_item(const Sales_item rhs); // error, 进入死循环

60. 禁止复制

为了防止复制,类必须显示声明其复制构造函数为private

61. 不可重载的操作符

::  .* . ?:

内置类型操作符不可重定义

大多数重载操作符可以定义为普通非成员函数或类的成员函数

注:作为类成员的重载函数,其形参看起来比操作数数目少1。作为成员函数的操作符有一个隐含的this形参,限定为第一个操作数。

重载一元操作符如果作为成员函数就没有(显示)形参,如果作为非成员函数就有一个形参。类似的,重载二元操作符定义为成员时有一个形参,定义为非成员函数时有两个形参。

类对象作为右操作数时,只能使用非成员函数去重载。例如operator<<

    friend ostream& operator<<(ostream& os, const Foo& f) { // 非类成员函数        os << f.ii;        return os;     } 
输入操作符和输出操作符的区别:输入操作符必须处理错误和文件结束的可能性
istream&operator>>(istream& in, Sales_item& s) {    double price;    in >> s.isbn >> s.units_sold >> price;    if (in) // is if ok?        s.revenue = s.units_sold*price;    else        s = Sales_item(); // input failed:reset object to default state    return in;}
定义==操作符,也应该定义!=操作符
下标操作符必须定义为类成员。类定义下标操作符时,一般需要定义两个版本,一个const并返回const引用,一个非const并返回引用
自增/自减操作符

CheckedPtr& operator++(); // prefix operatorsCheckedPtr& operator--();CheckedPtr operator++(int); // postfix operatorsCheckedPtr operator--(int);// 注:后缀式里面int形参  无实际意义,唯一的作用是来区分前后。而且需要注意的是:后缀返回值,前缀返回自引用
62. 对()操作符的使用
class Clastype  {      public:          Clastype(int a)          {              cout << "Hello Clastype!" << a << endl;          }          bool operator ()(int b)          {              cout << "Hello Clastype()!" << b << endl;              return true;          }  };  int main()  {      Clastype a(1);      Clastype(2);      Clastype t = Clastype(3);      t(4);      Clastype *b = new Clastype(5);      (*b)(6);  }

63. 转换操作符时一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换函数采用如下通用形式:

operator type();

转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。

class SmallInt {public:    operator int() const{return val;};};int main() {    SmallInt si;    double dval;    si >= dval; // si converted to int and then converted to double}
64. 只能应用一个类类型转换

类类型转换之后不能再跟另一个类类型转换,如果需要多个类类型转换,则代码出错

int calc(int);Integral intVal;SmallInt si(intVal); // ok, convert intVal to smallInt and copy to siint i = calc(si); // ok, convert si to int and call calcint j = calc(intVal);// error, no conversion to int from Integral, it's error, from Integral to SmallInt and Then convert to SmallInt to int.
65. 类型转换造成的二义性
class LongDouble {    operator double();    operator float();};LongDouble ldObj;int ex1 = ldObj;float ex2 = ldObj;// 从double到int和从float到int没有先后之分,所以1是二义性的,2是ok的
例2.

class LongDouble {public:    LongDouble(double);    // ...};void calc(int);void calc(LongDouble);double dval;calc(dval); // which function?// 注:最佳为void calc(int),调用此函数是标准转换;调用void calc(LongDouble)是从double到LongDouble类型是类类型转换,因为标准类型转换优于类类型转换
66. 在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,如:

Item_base *baseP = &derived;double d = baseP->Item_base::net_price(42);
67. 恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松
class Base {public:    std::size_t size() const {return n;}protected:    std::size_t n;};class Derived : private Base {public:    using Base::size; // 只能开放size为publicprotected:    using Base::n; // 只能开放n为protected};
68. 友元不能继承。

基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。
69. 构造函数,赋值操作符。

赋值操作符与构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显示赋值。

Derived &Derived::operator=(const Derived *rhs) {    if (this != &rhs) { // 防止自身赋值        Base::Operator=(rhs); // 调用父类   就算不写也不会抛错    }    return *this;}
70. 即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数
为了正确的释放派生类中的对象,基类中的析构函数必须为虚函数。

71. 构造函数和赋值操作符不是虚函数
如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本
72. 非类型模板形参

template<class T, size_t N>void array_init(T (¶m)[N]) {    for (size_t i = 0; i != N; ++i) {        param[i] = 0;    }}int x[42];double y[10];array_init(x);array_init(y);
73. 模板实参推断

(1)多个类型形参的实参必须完全匹配

template<typename T>int compare(const T& v1, const T& v2) {    ....    return 0;}int main() {    short si;    compare(si, 1024); // ERROR!!!!!    return 0;}
如果设计者想允许实参的常规转换
template<typename A, typename B>int compare(const A& v1, const B& v2) {    return 0;}
(2) 类型形参的实参的受限转换

一般而言,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行2种转换:

const转换:

数组或函数到指针的转换:

74. 当形参为引用时,数组不能转换为指针,a和b的类型不匹配,所以调用将出错

void testParam(int& a, int& b) {    //....}int main(void){    int a[10], b[42];    testParam(a,b);    return 0;}
75. 非类型模板实参必须是编译时常量表达式
template<int h, int w>class Screen {public:    Screen():screen(hi * wid, "#"), height(hi),width(hi){}private:    std::string screen;    std::string::size_type height, width;};Screen<24, 80> hp2621; // 24,80是编译时常量

76. 模板特化

意义:某些情况下,通用模板定义对于某个类型可能是完全错误的,例如

template<typename T>int compare(const T &v1, const T &v2) {    if (v1 < v2) return -1;    if (v2 < v1) return 1;    return 0;}
如果要比较const char*,上边的compare就不正确了

(1) 关键字template后面接一对空的尖括号(<>)

(2) 再接模板名和一对尖括号,尖括号指定这个特化定义的模板形参

(3) 函数形参表

(4) 函数体

template<>int compare<const char *>(const char* const &v1, const char* const &v2) {    return strcmp(v1, v2);}

0 0
原创粉丝点击