cpp-primer-5th
Table of Contents
- Chapter 01: Getting Started
- Chapter 02: Variables and basic types
- Chapter 03: Strings, Vectors, and Arrays
- Chapter 04: Expressions
- Chapter 05: Statements
Chapter 01: Getting Started
Writing a Simple C++ Program
- 每个cpp程序都有一个或者多个function, 而且其中一个function的名字必须是main,下
面就是一个最简单的cpp程序,什么都不做就是返回零
int main() { return 0; }
- 在大多数系统中, 返回值为0意味着success, 而nonzero意味着失败
Compiling and Executing Our Program
- cpp文件可以使用如下的后缀:
- cc
- cxx
- cpp
- cp
- C
- 在类Unix系统中,我们使用如下的一条命令来把"源代码"编译成"可执行程序"
CC prog1.cc
- 编译完成的程序,会默认使用a.out这个名字(在windows上面mingw系统编译成a.exe)
- 运行就直接调用这个a.out就可以了.使用echo $?可以查看main函数的返回值
c:/code/cpp-in-action/primer5/ch01 >>> CC 01.cc c:/code/cpp-in-action/primer5/ch01 >>> ./a.exe c:/code/cpp-in-action/primer5/ch01 >>> echo $? 0
- Windows上面是查看ERRORLEVEL
> echo %ERRORLEVEL% 0
- 如果你不想使用a.out或者a.exe,那么可以使用-o参数指定自己的"可执行程序"的名
字
c:/code/cpp-in-action/primer5/ch01 >>> ls 01.cc c:/code/cpp-in-action/primer5/ch01 >>> g++ -o 01 01.cc c:/code/cpp-in-action/primer5/ch01 >>> ls 01.cc 01.exe
A First Look at Input/Output
- cpp是通过一个叫做iostream的standard library来处理IO.所谓stream就是一系列的 从IO设备读入或输出的字符串
- 处理IO的object主要有:
- istream类型的object : cin处理输入
- ostream类型的object : cout处理输出
- ostream类型的object : cerr处理warning和error的输出
- ostream类型的object : clog处理总结信息等
- 我们下面看一个结合input和output的例子
#include <iostream> int main() { std::cout << "Enter two numbers:" << std::endl; int v1 = 0, v2 = 0; std::cin >> v1 >> v2; std::cout << "The sum of " << v1 << " and " << v2 << " is " << v1 + v2 << std::endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Enter two numbers: // // 3 7 // // The sum of 3 and 7 is 10 // ////////////////////////////////////////////////////
A Word about Comments
- c++ 兼容c的`/**/`注释方式
- 同时提供了`//`的行注释方式
Flow of Control
- flow of control和其他的语言类似,有两种:
- 知道循环次数的时候,使用for
#include <iostream> int main() { int sum = 0, val = 1; for (int val = 1; val <= 10; ++val) { sum += val; ++val; } std::cout << "Sum of 1 to 10 inclusive is " << sum << std::endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Sum of 1 to 10 inclusive is 55 // ////////////////////////////////////////////////////
- 不知道具体循环次数的时候,使用while
#include <iostream> int main() { int currVal = 0, val = 0; if (std::cin >> currVal) { int cnt = 1; while (std::cin >> val) { if (val == currVal){ ++cnt; } else { std::cout << currVal << " occurs " << cnt << " times" << std::endl; currVal = val; cnt = 1; } } // while loop ends here // remember to print the count for the last value in the file std::cout << currVal << " occurs " << cnt << " times" << std::endl; } return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 42 42 42 42 42 55 55 62 100 100 100 // // 42 occurs 5 times // // 55 occurs 2 times // // 62 occurs 1 times // // 100 occurs 3 times // ////////////////////////////////////////////////////
- 知道循环次数的时候,使用for
Introducing Clases
- 在cpp中,我们可以定义自己的类型:通过关键字class. cpp的特点是,我们自己定义的 类型,和内置类型拥有几乎一样的"性质"
Chapter 02: Variables and basic types
Primitive Build-in Types
- cpp定义的内置类型(primitive type)包括了:
- 算术类型(arithmetic type)
- 特殊类型(void)
Arithmetic Types
- 算术类型在规范里面,只给了"最小"size, 因为机器字符宽度不同,所导致的最大长度
并不相同.下面是规范规定的最小size
Type Meaning Minimum Size bool boolean 8 bits char character 16 bits wchar_t wide character 16 bits char16_t Unicode character 16 bits char32_t Unicode character 32 bits short short integer 16 bits int integer 16 bits long long integer 32 bits long long long integer 64 bits float single-precision floating-point 6 significant digits double double-precision floating-point 10 significant digits long double extended-precision floating-point 10 significant digits
Type Conversions
- type conversion在cpp中是非常容易发生的,比如
bool b = 42; // b is true int i = b; // i has value 1 i = 3.14; // i has value 3 double pi = i; // pi has value 3.0 unsigned char c = -1; // assuming 8-bit chars, c has value 255 signed char c2 = 256; // assuming chars, the value of c2 is undefined
Variables
Variable Definitions
- 变量的定义很简单
int sum =0; Sales_item item; // item has type Sales_item
- 如果一个变量在定义的时候还有有初始值,我们就说它被initialized了
// ok: price is defined and initialized before it is used to initialize discount double price = 109.99; // ok: call applyDiscount and use the returen value to initialized salePrice double salePrice = applyDiscount(price, discount);
- 需要注意的是Initialization和assignment并不相同
Initialization happens when a variable is given a value when it is created. Assignment obliterates an object's current value and replaces that value with a new one.
- initialization子所以复杂,是因为cpp定义了很多种"合理的"方法,比如,下面四种初
始化的方法都是合理的
int units_sold = 0; int units_sold = {0}; int units_sold{0}; int units_sold(0);
- {}来initialize的方式,是c++11才新近引入的, 名字叫做list initialization
- list initialization看似不常用,却有一个特殊的作用:编译器会检查初始化是否会
损失精度
#include <iostream> int main(int argc, char *argv[]) { long double ld = 3.14159; int a{ld}; int b = {ld}; std::cout << a << std::endl; std::cout << b << std::endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // $ g++ -std=c++11 01.cc // // 01.cc: In function 'int main(int, char**)': // // 01.cc:6:13: warning: narrowing conversion of // // 'ld' from 'long double' to 'int' inside { } // // [-Wnarrowing] // // int a{ld}; // // ^ // // 01.cc:7:16: warning: narrowing conversion of // // 'ld' from 'long double' to 'int' inside { } // // [-Wnarrowing] // // int b = {ld}; // // ^ // // $ ./a.out // // 3 // // 3 // ////////////////////////////////////////////////////
- 非list initialization通常是使用'='或者(), 都是可以通过编译并且没有warning的
#include <iostream> int main(int argc, char *argv[]) { long double ld = 3.14159; int c(ld), d = ld; std::cout << c << std::endl; std::cout << d << std::endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // $ g++ -std=c++11 02.cc // // $ ./a.out // // 3 // // 3 // ////////////////////////////////////////////////////
- 如果声明的时候没有给与一个initializer的话,那么会被给与一个default value:
- 如果是built-in类型的话:
- 定义在function body之外的,全部会被初始化为zero
- 定义在function body之内的,会被设置为unitialized状态.local static
object(built-in type)是唯一一个特例(虽然在某个function里面,但是在
声明的时候,必定初始化为0了)
#include <iostream> int count_calls() { static int count; return ++count; } int main(int argc, char *argv[]) { for (int i = 0; i < 3; i++) { std::cout << count_calls() << std::endl; } return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1 // // 2 // // 3 // ////////////////////////////////////////////////////
- 如果不是build-in类型的话:
- 如果class的定义允许我们不使用explicit initializer的话,当然可以直接定
义而不给initializer
std::string empty; // empty implicitly initialized to the empty string
- 有些class需要explicitly initialized,所以必须给与initializer才可以.
- 如果class的定义允许我们不使用explicit initializer的话,当然可以直接定
义而不给initializer
- 如果是built-in类型的话:
Variable Declarations and Definitions
- 为了能够让不同的文件来写不同的逻辑部分.c++支持separate compilation.也就是把 一个大的程序,分成多个不同的文件进行编译.
- 为了支持separate compilation, cpp区分了以下两个概念:
- 声明 (declaration) : 声明只会指出变量的名字和类型,不会分配内存. 可以简单
的理解为有extern而没有'='的语句.
extern int i; // declares but does not define i
- 定义 (definition) : 定义不仅仅会指出变量的名字和类型, 而且会分配内存, 甚
至给出初始值. 简单理解就是1只要有'='或者2只要没有extern的语句都是定义
// has '=', definition, memory is ready, has default value of 1 int i = 1; // has '=', even extern shown, it is also a definition extern double pi = 3.1416; // no extern, definition. memory is ready, but no default value int j;
- 声明 (declaration) : 声明只会指出变量的名字和类型,不会分配内存. 可以简单
的理解为有extern而没有'='的语句.
- 一个变量可以被声明很多次,但是只能被定义一次. 如果我们要在多个文件里面使用
同一个变量,那么我们要:
- 只在一个文件里面定义它(只一次)
- 在其他的使用这个变量的文件中,声明它(多次)
Identifiers
- 变量标示的要求在cpp里面和其他的语言没什么不同,不能以数字开头(防止和数字混淆)
- 关键字不能作为变量标示
Scope of a Name
- 作用域scope在cpp里面,大部分情况下是靠{}来分割的
- 相同的名字在不同的作用域里面可能代表不同的变量.名字从declare开始,可以被使用,
直到这个作用域(包含declare的scope)结束.
Names are visible from the point where they are declared until the end of the scope in which the declaration appears
- 我们在cpp里面建议在第一次'使用'这个变量的时候,才去定义它, 因为这样容易找到 变量定义(而不是在程序开始的地方), 同时这样容易给变量一个有意义的初始值
- scope也可以包含其他scope的:
- 被包含的scope叫做inner scope
- 包含其他scope的scope叫做outer scope
- 定义在outer scope的name可能会被inner scope里面的相同名字的变量所掩盖
#include <iostream> int reused = 42; // resued has global scope int main() { int unique = 0; // unique has block scope std::cout << "use global reused " << reused << " unique is " << unique << std::endl; int reused = 0; // new, local object named reused hides global reused std::cout << "use local reused " << reused << " unique is " << unique << std::endl; std::cout << "explicityly request global reused " << ::reused << " unique is " << unique << std::endl; return 0; } ////////////////////////////////////////////////////// // <===================OUTPUT===================> // // use global reused 42 unique is 0 // // use local reused 0 unique is 0 // // explicityly request global reused 42 unique is 0 // //////////////////////////////////////////////////////
Compound Types
- 所谓compund type定义如下
A compound type is a type that is defined in terms of another type.
- cpp中有一些compund type,其中最著名的两个:引用和指针.
References
- 首先要说明的是,一般来说reference就是指的lvalue reference, 虽然新出来一种rvalue reference
- 然后我们来看看reference的本质:其本质就是为一个object寻找一个alternative name
int ival = 1024; int &refVal = ival; // refVal refers to (is another name for) ival
- 我们来比较一下常规的definition和reference的definition之间的差距:
- 常规的definition : 我们定义的时候, 这个initializer的值就会被"拷贝"到我们 申请的内存(object)里面
- reference的definition: 我们定义的时候,是把reference给"bind"到initializer
(也就是ival).而且reference是一旦初始化,就无法再refer给其他的object.正是因
为无法rebind,所以reference必须在定义的时候赋予初始化值.也就是说下面这行
是不行的
//Error! int &refVal;
- 其实reference说来说去,只是给object起了一个新的名字而已!
A reference is not an object. Instead, a reference is just another name for an already existing object.
- 正是由于reference不是一个object,所以我们不能定义reference to reference.
- 在大多数情况下(两种特例是const和继承), ref自己的类型必须和initializer的类
型相同
double dval = 3.14; // Error; initializer must be an int object! int &refVal5 = dval;
- 而且不能把reference"绑定"道非object的对象, 比如literal或者是general expression
的结果
// Error! initializer must be an object int &refVal4 = 10;
Pointers
- Pointer是C时代的产物, Reference是cpp为了改变Pointer的某些危险特性而设计的,
所以,两者有很多异同点:
- 相同点: 都是compunt type, 而且是用来indirect acces to other objects
- 不同点: Pointer是一个object! 这就决定了很多point的"危险的"行为:
- pointer是可以被赋值和拷贝的, 而且可以被赋值很多次
- pointer定义的时候,甚至可以没有初始化值(因为可以被多次赋值)
- 如果pointer定义了以后,没有给与初始化值,那么这个pointer的值是undefined! 非常危险
- Pointer是用来"保存另外一个object的地址"的object.所以我们要用到取地址符(&),
千万不要和reference前面的`&`混淆:
- reference前面的&是为了区别'普通declarator'(比如ival) 和'reference declarator'
(比如refVal)而加的,是一个语法层面的标记
int ival = 1024; int &refVal = ival;
- pointer是指向某个object的地址的, 所以要在那个object前面加`&`, 这个`&`是一
个操作符,就和`^`一样,进行的是"取地址"操作. 当然了,为了区分'普通declarator'
(比如ival)和'pointer declarator'(比如p), 我们要在pointer 前面加个'*'
int ival = 42; int *p = &ival;
- reference前面的&是为了区别'普通declarator'(比如ival) 和'reference declarator'
(比如refVal)而加的,是一个语法层面的标记
- 还是除了const和继承以外,pointer类型必须和initializer相同.
double dval; double *pd = &dval; // ok: initializer is the address of a double double *pd2 = pd; // ok: initializer is a pointer to double int *pi = pd; // error: types of pi and pd differ pi = &dval; // error: assigning the address of a double to a pinter to int
- Pointer的值是存储的是address,这个地址值可以有如下四种可能:
- 这个地址可以是指向一个object
- 这个地址可以指向一个object后面的位置
- 这个地址可以为了null,表示pointer没有指向任何object
- 这个地址可以是invalid的(比如定义没有给初始化的情况下), 这种情况是最危险的.
- 前面介绍了取地址操作符`&`, 而把地址还原成object(dereference)的符号是`*` ,
需要注意的是我们一定要对valid的地址进行derefernce操作
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int ival = 42; int *p = &ival; // * yields the object to which p points cout << *p << endl; // * yields the object; we assign a new value to ival throught p *p = 0; cout << *p << endl; cout << ival << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 42 // // 0 // // 0 // ////////////////////////////////////////////////////
- 如果一个指针不指向任何的object,我们把这个指针叫做null poiner.可以在定义的
时候通过如下方法来定义null pointer
#include <iostream> #include <cstdlib> int main(int argc, char *argv[]) { ////////////////////////////////////////////////// // use to include nullptr g++ --std=c++11 06.cc // ////////////////////////////////////////////////// int *p1 = nullptr; int *p2 = 0; NULL is include in cstdlib /////////////////////////////////////////////////////////// // Modern C++ programs generally should avoid using NULL // // and use nullptr instead // /////////////////////////////////////////////////////////// int *p3 = NULL; return 0; }
- 虽然NULL在预定义的时候,其值就是0, 但是pointer确不接受integer作为它的rvalue,
因为它会以为你是想要把'这个int的地址'赋予给这个pointer.所以上面三种才是给null
pointer 赋值(或初始化的方式)
#include <iostream> #include <cstdlib> int main(int argc, char *argv[]) { int *p4 = NULL; int zero = 0; /////////////////////////////////////////////////////////////////////////////// // 07.cc:9:15: error: invalid conversion from 'int' to 'int*' [-fpermissive] // // int *p5 = zero; // // ^ // /////////////////////////////////////////////////////////////////////////////// int *p5 = zero; return 0; }
- 因为pointer是可以赋值的,所以任何NULL值的pointer都是false,否则都是true
- 在范型还是不是很成熟的c时代,为了能够应对不同类型的pointer,发明了void* pointer
这种pointer可以指向任何的类型, 而且可以和其他pointer比较(因为都是地址么),
但是不可以对其进行dereference操作,进而控制响应object(因为不知道地址对应的
类型是什么,所以无法决断拿几个bytes的地址)
#include <iostream> using namespace std; int main(int argc, char *argv[]) { double obj = 3.14, *pd = &obj; void *pv = &obj; pv = pd; ///////////////////////////////////////////////////////////////// // 08.cc:10:14: error: 'void*' is not a pointer-to-object type // // cout << *pv << endl; // // ^ // ///////////////////////////////////////////////////////////////// cout << *pv << endl; return 0; }
Understanding Compound Type Declarations
- pointer和ref的定义方式非常令人困惑,是因为如下两种方式都是合理的
int *p1; // Way1: p1 is a pointer to int int* p2; // Way2: p2 is a pointer to int
- 我们认为第二种定义(Way2)方式不好,因为它看起来好像definition的任务是定义了 一个int* 类型,其实不是的`*`是用来形容p2的, 这个definition的主体是一个int类 型.
- 第二种定义方式在一次定义超过一个变量的时候会更加的难以区别
int* p1, p2; // p1 is a pinter to int; p2 is an int
- 所以我们决定在后面都使用第一宠定义方式(Way1).
- 因为pointer是一个object, 所以它自己也是有地址的,所以我们也可以让另外一个 pointer指向它, 也就是我们常说的pointer to pointer
- 既然我们使用`*`来标示此变量为指针,那么我们也可以连续的使用多个`*`来标示"指针
的指针",甚至"指针的指针的指针"
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int ival = 1024; int *pi = &ival; // pi points to an int int **ppi = π // ppi points to a pointer to an int cout << "The value of ival\n" << "direct value: " << ival << "\n" << "indirect value: " << *pi << "\n" << "doubly indirect value: " << **ppi << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // The value of ival // // direct value: 1024 // // indirect value: 1024 // // doubly indirect value: 1024 // ////////////////////////////////////////////////////
- 因为reference不是一个object,所以我们不能让pointer指向reference, 但是我们却 可以为pointer创建reference
const Qualifier
- const的作用是防止变量被更改, 因为无法被更改,所以定义的时候,必须给与初始值
const int i = get_size(); // ok: initialized at run time const int j = 42; // ok: initialized at compile time const int k; // error: k is uninitialized const
- const所赋予变量的"常量性"只在它被改变的那个时候,才起作用,平时const int变量
和int变量并没有什么不同, 比如,我们可以给int类型j赋值,initializer为一个const
int 类型
int i = 42; const int ci = i; // ok: the value in i is copied into ci int j = ci; // ok: the value in ci is copied into j
- 鉴于const常量在定义的时候就必须赋予initializer值,那么单纯的"声明"一个常量就 不可能了, 所以在多个文件里面使用常量是不现实的,
- 默认情况下, 常量是local to a file的. 所以在不同文件里面的const常量如果重名 了也没有关系,因为它们相当于file_name::const_name
- 如果你想要在多个文件里面share常量,我们肯定要使用extern
- 但是我们不光要在"declare"里面使用extern,在"definition"那里也必须使用extern!
否则编译器会把你的const变量直接替换成数值(比如下例中,所有bufSize都直接替换成
1001), 下面就是definition里面也使用extern const的例子
extern const int bufSize = 1001; int getSize() { return bufSize + 1; }
- "declare"里面当然也得使用extern了.
#include <iostream> using namespace std; int main(int argc, char *argv[]) { extern const int bufSize; cout << bufSize << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // $ g++ -o a.out 11.cc 12.cc // // $ ./a.out // // 1001 // ////////////////////////////////////////////////////
References to const
- const type的object也是object,我们当然可以给一个object绑定一个reference啦,
但是这个reference既然绑定的是const变量,那么它本身是const变量的"另外一个名字"
也就必然无法被赋值了
const int ci = 1024; const int &r1 = ci; // OK: both reference and underlying object are const r1 = 42; // error: r1 is a reference to const
- 同样的,我们在初始化的时候,reference的类型也必须是const type的,否则无法初始
化成功
const int ci = 1024; int &r2 = ci; // error: non const reference to a const object!
- 如果铭记reference不是object这个事实,我们就很容易理解, const是无法修饰reference 的.
- 而reference无法改变初始化binding的对象这个特点,其实让reference本身具有了"const" 属性, 所以reference to什么类型只影响到我们能对reference做什么.而不影响reference binding的这个过程.
- 前面我们说过,有两种情况下,reference的类型和它要bind的类型可以不"完全match"
这里我们就遇到了第一种: reference to const X类型可以接受"所有能够converted to
X类型"的类型作为自己的initializer, 比如
#include <iostream> using namespace std; int main(int argc, char *argv[]) { double dval = 3.14; //////////////////////////////////////////////////////////////// // We can initialize a reference to const from any expression // // that can be converted to the type of reference // //////////////////////////////////////////////////////////////// const int &ri = dval; cout << "ri is " << ri << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // ri is 3 // ////////////////////////////////////////////////////
- 如果深究这个问题, 我们可以如下理解, 初始化一个refer to const, 如果类型不符
的话,编译器会做如下的转换,以使得reference to const int 确实bind了一个const
int类型
const int temp = dval; // create a temporary const int from the double const int &ri = temp; // bind ri to that temporary
- 也就是说编译器创建了一个新的"临时变量". 然后让这个ref to const 来bind这个 临时变量. 这个临时变量是永远找不到了,但是我们有它的ref,也就相当于有它了.
- 这种赋值,只有在reference to const的时候才有意义. 假设我们的ref不是const的, 那么我们的const ri bind了一个double的变量, 如果我们ri变成了4(本来是3), double 应该怎么变呢?变成4.14? 说不清楚,所以只有ref to const有这种便利.
- 正是这种便利,让函数的参数更喜欢用const Type &作为参数类型, 因为:
- 如果实参不需要转换成Type,那么正好减少一次对象复制. 而且这里还用到ref to const
的另外一个特性: ref to const 可以bind一个根本就不是const的对象. 实参不是
const的, 我参数也给它搞成ref to const. 这样可以保证不更改原来实参的值(同
时可以减少一次对象拷贝)!
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int i = 42; int &r1 = i; const int &r2 = i; cout << "r1 is " << r1 << " and r2 is " << r2 << endl; r1 = 0; cout << "r1 is " << r1 << " and r2 is " << r2 << endl; /////////////////////////////////////////////////////////////// // 14.cc:15:8: error: assignment of read-only reference 'r2' // // r2 = 23; // // ^ // /////////////////////////////////////////////////////////////// // r2 = 23; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // r1 is 42 and r2 is 42 // // r1 is 0 and r2 is 0 // ////////////////////////////////////////////////////
- 如果实参需要转换成Type, 那么就产生一个临时的const变量.
- 如果实参不需要转换成Type,那么正好减少一次对象复制. 而且这里还用到ref to const
的另外一个特性: ref to const 可以bind一个根本就不是const的对象. 实参不是
const的, 我参数也给它搞成ref to const. 这样可以保证不更改原来实参的值(同
时可以减少一次对象拷贝)!
Pointers and Const
- 就像reference一样,我们可以定义pointer to const来bind任意一个类型的变量(只要 可以convert).
- pointer (ref) to const绑定了某个变量后, 这个变量是可以变的.但是无法通过ref
或者pointer来改变. 也就是说,ref(pointer)'认为'自己绑定了const变量(虽然并不
一定是)
It may be helpful to think of pointers and references to const as pointers or references "that *think* they point or refer to const"
- pointer是object这个悲催的事实,让pointer自己也有可能是const的.(也就是说初始
化的时候绑定了什么变量,就不能再变了). 我们是通过把const放到*后面来强调pointer
本身的const性的(因为const离pointer更近了, 从右往左念就是"p是一个const pointer")
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a1 = 0; int a2 = 1; const int *p1 = &a1; cout << *p1 << endl; p1 = &a2; cout << *p1 << endl; int *const p2 = &a1; cout << *p2 << endl; ////////////////////////////////////////////////////////////// // 15.cc:16:8: error: assignment of read-only variable 'p2' // // p2 = &a2; // // ^ // ////////////////////////////////////////////////////////////// // p2 = &a2; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 // // 1 // // 0 // ////////////////////////////////////////////////////
Top-Level const
- 从一个更高的高度来审视const,我们会发现const其实是有两种:
- top-level const: 表示object本身是const, 这种const可以对任何一种object有效 比如: int类型, string类型, class类型,都可以自身变成const
- low-level const: 表示compound type类型所指向的object是const的.既然是为compound type类型所准备的,所以自然只能适用于reference和pointer
- pointer是唯一一个可以同时拥有top-level和low-level const属性的类型
- pointer自己是const的: top-level const
int i = 0; int *const p1 = &i;
- pointer指向的object是const的: low-level const
const int ci = 42; const int *p2 = &ci;
- 同时具有top-level和low-level const
const int ci = 42; const int *const p3 = &ci;
- pointer自己是const的: top-level const
- 区分top-level和low-level const的原因:是在于拷贝的时候:
- 当我们进行拷贝的时候, top-level const一直是被ignore的
const int ci = 42; int i = ci; // ok: copying the value of ci; top-level const in ci is ignored const int *const p3 = &ci; const int *p2 = p3; // ok: pointed-to type matches; to-level const in p3 is ignored
- 当我们进行拷贝的时候, low-level const是绝对不能忽视的!
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int i = 11; const int *p1 = &i; //////////////////////////////////////////////// // 16.cc:8:15: error: invalid conversion from // // 'const int*' to 'int*' [-fpermissive] // // int *p2 = p1; // // ^ // //////////////////////////////////////////////// // int *p2 = p1; return 0; }
- 当我们进行拷贝的时候, top-level const一直是被ignore的
constexpr and Constant Expressions
- 所谓constant expression就是value无法改变,而且在编译阶段就可以计算值的表达式
- literal肯定是constant expression
- 一个object(或者expression)是不是const expression主要取决于两点:
- object的类型:
////////////////////////////////////////////////////////////////// // alghough staff_size is initialized from a literal, it is not // // a const because it is a plain int, not a const int // ////////////////////////////////////////////////////////////////// int staff_size = 27;
- initializer
////////////////////////////////////////////////////////////////////////// // even though sz is a const, the value of its initializer is not known // // until run time, hence, sz is not a constant expression // ////////////////////////////////////////////////////////////////////////// const int sz = get_size();
- object的类型:
- 新的时代,如果我们认为我们的initializer是一个const expression的话,我们可以
使用constexpr来代替const来修饰我们的类型
Variables declared as constexpr are implicitly const and must be initialized by const expressions.
- 一般来说constexpr的initializer必须是const expression, 但是同时也可以是新规
范里面引入的constexpr function
#include <iostream> using namespace std; //constexpr function constexpr int size() { return 15; } int main(int argc, char *argv[]) { constexpr int mf = 50; // constexpr constexpr int sz = size(); cout << mf << endl; cout << sz << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 50 // // 15 // ////////////////////////////////////////////////////
- 一般来说,能够组成constexpr的initializer的类型都很有限, 绝大多数情况下就是
能够产生literal value的类型, 所以我们把constexpr里面使用的类型(除了类型,当
然也可以直接使用数字)叫做literal type
The types we can use in a constexpr are known as "literal types" because they are simple enough to have literal values.
- 我们目前了解的类型里面, 如下三种就是literal 类型
- arithmetic
- reference
- pointer
- 如果我们想把一个pointer定义为一个constexpr的话,在语法方面是没有问题的,但是
要注意选对initializer:
- 0 或者 nullptr肯定是可以的
- 定义在function内部的变量并不是存在于固定地址的, 所以其地址不可以作为constexpr的初始值
- 定义在所有function之外的变量地址固定,所以可以使用
- 还有第六章会介绍的variable that exist across calls to that function#TODO#
- 还有一个非常人格分裂的地方要解释,那就是 constexpr如果修饰一个指针的话,它给
与的是top-level的const(也就是相当于在*后面的const, 但是由于各种历史原因,
constexpr只能放倒最前面)
const int *p = nullptr; // p is a pointer to a const int constexpr int *q = nullptr; // q is a const pointer to int
Dealing with Types
- c语言中有时候类型过于复杂,我们需要一些简化手段,让读代码的人更容易看清楚类型
Type Aliases
- type alias是用来给与变量新名字的方法
- 老的c语言使用了typedef
typedef double wages; //wages is a synonym for double
- 新的规范使用了using来进行alias declaration
using SI = Sales_item; // SI is a synonym for Sales_item
- typedef是c语言引入的,typedef和compund type结合, 再加上const参与的话,会有很
多意想不到的结果
#include <iostream> using namespace std; int main(int argc, char *argv[]) { typedef char *pstring; const pstring cstr = 0; // cstr is a constnt pointer to char!! // NOT equal with previous one const char* cstr2 = 0; // cstr2 is a pointer to const char return 0; }
The auto Type Specifier
- 有时候,我们在程序中很难判断一个对象的真实类型,我们就会使用新规范引入的auto
关键字类让编译器帮助我们判断类型. 编译器判断的依据是initializer, 所以auto
修饰的变量必须得提供initializer
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int val1 = 3; int val2 = 4; auto item = val1 + val2; cout << item << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 7 // ////////////////////////////////////////////////////
- auto在同一行,只能代替一种类型.不能既修饰int又修饰double
auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi
- auto的行为和普通int没区别,也是会ignore top-level const, 除非你指定
const int ci = i; auto b = ci; // b is an int (top-leve const in ci is dropped) const auto f = ci; // deduced type of ci is int; f has type const int
The decltype Type Specifier
- decltype是用来让编译器判断类型的#TODO#
Chapter 03: Strings, Vectors, and Arrays
Namespace using Declarations
- 每次我们输入cout, endl这种函数或者变量的时候, 其实我们都是在std这个namespace
里面的. 所以我们可以使用using来默认使用某个namespace
using std::cin;
- 如果我们只想使用std里面的某几个函数或者变量,我们可以分别指定,而不至于引入std
里面的所有的名字
#include <iostream> using std::cin; using std::count; using std::endl; int main(int argc, char *argv[]) { int val1 = 3; int val2 = 4; auto item = val1 + val2; cout << item << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 7 // ////////////////////////////////////////////////////
- 为了防止命名冲突, 在header里面不要使用using
Library string Type
- string是cpp里面的变长字符串,需要下面两行引入得以运行
#include <string> using std::string;
Defining and Initializing strings
- string常见的初始化方式如下
string s1; // default initialization; s1 is the empty string string s2 = s1; // s2 is a copy of s1 string s3 = "hiya"; // s3 is a copy of the string literal string s4(10, 'c'); // s4 is cccccccccc
- 初始化方式其实是包含了两种:
- copy initialization:
string s5 = "hiya";
- direct initialization:
string s6("hiya"); string s7(10, 'c');
- copy initialization:
Operations on strings
- 我们使用std::cin, std::cout来读取string
std::string s; std::cin >> s; std::cout << s;
- 要读取不定数量的字符串使用while循环
#include <iostream> using std::cin; using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { string word; while (cin >> word) { cout << word << endl; } return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // > ./a.out // // 123 456 789 // // 123 // // 456 // // 789 // ////////////////////////////////////////////////////
- 上面方法的坏处是,遇到空格就认为是一次输入的结束,然后就会打印一行,我们有时候
还是希望留有输入的空格的,那么我们就要配合上getline
#include <iostream> using std::cin; using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { string word; while (getline(cin, word)) { //////////////////////////////////////////////////////////////// // The newline that cuses getline to return is discarded; the // // newline is not stored in the string // //////////////////////////////////////////////////////////////// cout << word << endl; } return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // > ./a.out // // 123 456 789 // // 123 456 789 // ////////////////////////////////////////////////////
- string的empty函数用来判断字符串是否为空, size来返回字符串的长度
#include <iostream> using std::cin; using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { string word = "hello world"; cout << word.empty() << endl; cout << word.size() << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 // // 11 // ////////////////////////////////////////////////////
- 历史上string的size()返回的是string::size_type, 这是为了跨平台, 而且可以能够 容纳任意字符串的长度,其类型也是unsigned的
- 新规范让我们可以使用auto来存储返回值(自动判断成为size_type)
auto len = line.size(); // len has type string::size_type
- 因为size()返回值是unsigned的,而unsigned类型和signed类型混用的话,会有很多隐
含的问题,比如无符号和有符号数比较,总是小于.
#include <iostream> using std::cin; using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { int n = -5; string word = "hello world"; cout << word.size() << endl; cout << (word.size() > n ? "size is bigger than -5 !": "size is small than -5!") << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 11 // // size is small than -5! // ////////////////////////////////////////////////////
- cpp中的字符串赋值, 是和java不一样的,赋值是一种内容的拷贝,而不是多个ref指向
一个object
string st1(10, 'c'), st2; //assignment : replace contents of st1 with a copy of st2 // both st1 and st2 are now the empty string st1 = st2;
- 把两个string 对象加起来会形成一个新的string对象
#include <iostream> using std::cin; using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { string s1 = "hello", s2 = "world\n"; string s3 = s1 + s2; cout << s3; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // helloworld // ////////////////////////////////////////////////////
- 这个道理看起来简单,但是由于历史原因(主要为了兼容c), string literals竟然"不
是" standard library strings!!, 所以在使用'+'号的时候,'+'左右两边必须有一
边不是string literal
string s4 = s1 + ", "; // ok: adding a string and a literal string s5 = "hello" + ", "; // error! no string operand string s6 = s1 + ", " + "world"; // ok: each + has a string operand string s7 = "hello" + ", " + s2; // error! can't add string literals
Dealing with the Characters in a string
- 我们在处理字符串的时候,很经常的情况,就是处理字符串里面的每一个字符:
- 如果需要处理每一个字符的话,可以用'foreach'类型的for:
- for的每一个成员都是普通auto类型
#include <iostream> using namespace std; int main(int argc, char *argv[]) { string str("some s"); for (auto c: str) { cout << c << endl; } return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // s // // o // // m // // e // // // // s // ////////////////////////////////////////////////////
- for的每一个成员都是auto refrence类型, 这样可以用来更改字符串!
#include <iostream> using namespace std; int main(int argc, char *argv[]) { string str("hello world"); for (auto &c: str) { // c is a reference, the assignment changes the char in s c = toupper(c); } cout << str << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // HELLO WORLD // ////////////////////////////////////////////////////
- for的每一个成员都是普通auto类型
- 如果不需要处理某个字符的话,可以使用operator[]
#include <iostream> using namespace std; int main(int argc, char *argv[]) { string str("hello world"); if (!str.empty()) { str[0] = toupper(str[0]); } cout << str << endl; return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Hello world // ////////////////////////////////////////////////////
- 如果需要处理每一个字符的话,可以用'foreach'类型的for:
Library vector Type
- vector是一系列object的collection. 每个object的类型是一致的.这也就是我们常说
的container. 使用vector需要下面两个头文件
#include <vector> using std::vector;
- c++中的template有两类:
- class template: vector就是class template
- function template
- template本身不是class或者function, 但是它们是一种"指导"编译器生产class或者 function的办法!而编译器生成class或者function的这个过程叫做instantiation.
- 对于class template来说, 你可以通过在<>里面提供额外的信息来"指导"compiler针对
某个class来instantiate. 对于vector来说,就是在<>里面提供我们想要在里面存储的
类型
vector<int> ivec; // ivec holds objects of type int vector<Sales_item> Sales_vec; // holds Sales_items ///////////////////////////////////////////////////////////////////////// // old compiler may require old-style declaration for vector of vector // // like -> vector<vector<int> > // ///////////////////////////////////////////////////////////////////////// vector<vector<string>> file; // vector whose elements are vectors
- 因为reference不是一种类型,所以无法定义保存reference的vector
Defining and Initializing vectors
- 最简单的是定义一个空的vector
vector<string> svec; // default initialization; svec has no elements
- 当然也可以使用operator=或者copy构造函数来初始化
vector<int> ivec; vector<int> ivec2(ivec); vector<int> ivec3 = ivec;
- 新规范提供了list initialization,就是在{}里面提供多个初始化值
vector<string> articles = {"a", "an", "the"};
- 如果某个vector里面的值是一样的,我们可以使用如下方法定义
vector<int> ivec(10, -1); // ten int elements, each initialized to -1 vector<string> svec(10, "hi!"); // ten strings; each element is "hi!"
- 如果<>里面的类型允许"default value"的话,我们可以不提供初始化值,而只提供容器
大小
vector<string> svec(10); // ten elements, each an empty string
- 在vector初始化的时候,使用{}还是()有很大的学问, 在不同类型中情况也不一样:
- 比如在int类型中:
vector<int> v1(10); // v1 has then element with value 0 vector<int> v2{10}; // v2 has one element with value 10 vector<int> v3(10, 1); // v3 has ten elements with value 1 vector<int> v4{10, 1}; // v4 has two elements with values 10 and 1
- 如果你就此认为{}就会是list initialization的话,那就大错特错了. 比如类型是
string的话, {}在无法转换成list initialization的话, 也是可能会转换成()的
vector<string> v5{"hi"}; // list initialization vector<string> v6("hi"); // error: can't construct a vector from astring literal vector<string> v7{10}; // v7 has ten default-initialized elements vector<string> v8{10, "hi"} // v8 has ten elements with value "hi"
- 比如在int类型中:
Adding Elements to a vector
- 除了某些情况(比如所有vector的成员都是一个值),大多数情况下最有效率的增加vector
成员的方法就是先初始化一个空的vector,然后往里面加
vector<int> v2; for (int i = 0; i != 100; ++i) { v2.push_back(i); // append sequential integers to v2 }
- 而在for的里面对vector 的size进行更改的做法通常都会引入问题#TODO
Other vector Operations
- vector的每一个element就像string的每一个character一样可以访问, 比如下面的例
子就是更改vector的每个成员并打印
#include <iostream> #include <vector> using std::cout; using std::endl; using std::vector; int main(int argc, char *argv[]) { vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9}; for (auto &i : v) { i *= i; } for (auto i : v) { cout << i << " "; } cout << endl; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1 4 9 16 25 36 49 64 81 // ////////////////////////////////////////////////////
- vector也有empty和size, 而且特别要注意的是vector的返回值不是简单的size_type
而是带有<>类型的size_type
vector<int>::size_type //ok vector::size_type //error
- 我们可以使用[]来取用"已有"的vector数据, 但是绝对不能依靠[]来增加数据,比如下
面的例子就是错误的ivec是空的,for内部应该使用push_back,而不是[]
vector<int> ivec; // empty vector for (decltype(ivec.size()) ix = 0; ix != 10; ++ix) { ivec[ix] = ix; // disaster: ivec has no elements }
Introducing Iterators
- 虽然我们可以使用[]来遍历string或者vector, 但是我们可以使用更加"通用"的遍历 方法iterator.
- 以后我们会发现,std实现了一系列的容器,所有的容器都支持iterator,但是只有个别 的容器支持[]
- iterator和pointer类似,给了我们indirect访问object的能力,也有valid和invalid之分
- string不算一种容器,但是string的iterator和容器的iterator相似,我们后面的讨论 也都适用于string
Using Iterators
- iterator不像pointer一样,使用取地址符来赋值,它使用一些容器的成员函数来赋值:
- begin()返回:容器的第一个成员(如果存在的话)位置
- end()返回:容器的最后一个成员后面的位置
- 如果一个容器是空的话begin()和end()返回同一个值"容器最后一个成员后面的位 置"
- 和指针一样, iterator也支持一些个比较操作符比如"=="和"!="
- 和指针一样, iterator也支持"解引用"来获取指其所向位置的object. 当然我们的解
引用(dereference)也必须是valid的iterator
#include <iostream> #include <vector> using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { string s("some string"); if (s.begin() != s.end()) { auto it = s.begin(); *it = toupper(*it); } cout << s << endl; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Some string // ////////////////////////////////////////////////////
- 移动一个iterator是依靠"++", 或者"–"和pointer也类似
- 但是特别要注意的是因为end()所返回的iterator并不指向某个object,所以对它进行 "++"或者"–"都是逻辑不正确的.
- 这也是为什么cpp的iterator循环,使用的是'!=' 而不是'<'的原因: end()的位置"飘
忽"无法判断其大小. 另外的原因是某些iterator就没有支持operator<
#include <iostream> #include <vector> using std::cout; using std::endl; using std::string; int main(int argc, char *argv[]) { string s("some string"); for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) { *it = toupper(*it); } cout << s << endl; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // SOME string // ////////////////////////////////////////////////////
- 因为iterator也是object,所以它其实也是有const性的, 一个const的iterator是只
可以读不可以写的.就像const pointer一样:
- 如果一个容器是const的,那么'只能'使用const_iterator
vector<int>::const_iterator it; // const iterator
- 如果一个容器不是const的,那么'既'使用const_iterator, '又'可以使用普通iterator
vector<int>::iterator it; // common iterator
- 如果一个容器是const的,那么'只能'使用const_iterator
- begin() end()也会根据自己容器的类型来返回不同类型的iterator
vector<int> v; const vector<int> cv; auto it1 = v.begin(); // it1 has type vector<int>::iterator auto it2 = cv.begin(); // it2 has type vector<int>::const_iterator
- 这种设计非常不人性,很容易用错. 遍历的时候,我们通知都会喜欢使用const iterator,
而无论容器是否const,都可以使用const iterator, 所以新规范设计了新的cbegin和cend
来"必定"返回const iterator
vector<int> v; auto it3 = v.cbegin(); // it3 has type vector<int>::const_iterator
- 调用iterator指向的object的对象的函数有两种方法:
- 把iterator看成指针,可以使用*iterator来找到相应object,并调用.但是要记得加
括号
(*it).empty()
- 可以使用->来简化这个过程
it->empty()
- 把iterator看成指针,可以使用*iterator来找到相应object,并调用.但是要记得加
括号
- 我们前面说过,不要再for里面增加数据, 现在我们还要告诉大家,一旦增加vector的 size(比如通过push_back),所有的iterator都失效.所以,所有的loop一旦使用了iterator 那么就不要add element.
Iterator Arithmetic
- 和指针一样.iterator是可以进行加减法的.
- 一个利用iterator来进行二分查找的例子
// text must be sorted // beg and end will denote the range we're searching auto beg = text.begin(), end = text.end(); auto mid = text.begin() + (end - beg) / 2; while (mid != end && *mid != sought) { if (sought < *mid) { end = mid; } else { beg = mid + 1; } mid = beg + (end - beg) / 2; }
Arrays
- 和vector不同的是, array是fix-sized, 因为array的size fix,所以性能会比较有优势
Defining and Initializing Built-in Arrays
- 和reference以及pointer一样, 数组是一种compound type,形如a[d],其中d必须是
constant expression
unsigned cnt = 42; // not a constant expression constexpr unsigned sz = 42; // const expression int arr[10]; // array of ten ints int *parr[sz]; // array of 42 pointers to int string bad[cnt]; // error: cnt is not a constant expression string strs[get_size()]; // ok if get_size is constexpr, error otherwise
- array里面的成员都是default initialized的,所以在function里面的定义的built-in
类型数组是非常危险的,因为
As with variables of built-in type, a default-initialized array of built-in type that is defined inside a function will have undefined values
- array包含的必须是object,所以reference是没有数组的
- 除了字符数组以外的数组初始化方式如下
const unsigned sz = 3; int ial[sz] = {0, 1, 2}; // array of three ints with values 0, 1, 2 int a2[] = {0, 1, 2}; // anarray of dimension 3 int a3[5] = {0, 1, 2}; // equivalent to a3[] = {0, 1, 2, 0, 0} string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""} int a5[2] = {0, 1, 2}; // error: too many initializers
- 字符数组的初始化方式的一个特别之处是string literals会在最后加一个'\0'
char a3[] = "C++"; // null terminator added automatically const char a4[6] = "Daniel"; // error:no spaces for the null!
- 字符串数组非string literals的话,和普通数组没啥区别
char a1[] = {'c', '+', '+'}; char a2[] = {'c', '+', '+', '\0'};
- 我们不能通过一个数组来初始化另外一个数组, 也不能数组间相互赋值
int a[] = {0, 1, 2}; int a2[] = a; // error: can not initialize one array with another a2 = a; // error: cannot assign one array to another
- 数组的declaration是非常非常的多变和难以理解, 指针所谓的从右到左变得不再那
么好用. 数组是从'数组名'开始, 从里向外读取比较合适:
- array of pointers
int *ptrs[10];
- NO array of references!
// error!! int &refs[10] = /* ? */
- Parray pionts to an array of ten ints
int (*Parray)[10] = &arr;
- arrRef refers to an array of ten ints
int (&arrRef)[10] = arr;
- array of pointers
Access the Element of Array
- cpp是不会去检查数组越界的,而这是很多错误的源泉.
Pointers and Arrays
- 在cpp中, 编译器通常是将array转换成pointer去操作, 比如下面的例子nums即是一个
数组名,同时也是一个指针名
string nums[] = {"one", "two", "three"}; string *p2 = nums; // equivalent to p2 = &nums[0]
- 在c++11里面,下面的例子可以看出,编译器就是把数组转化成指针处理的
int ia[] = {0, 1, 2, 3, 4}; // ia is an array of ten ints auto ia2(ia); // ia2 is an int* that points to the first element in ia ia2 = 42; // error: ia2 is a pointer, and we can't assign an int to a pointer
- 值得注意的是decltype是不会默认把数组转换成指针的
decltype(ia) ia3 = {0, 1, 2, 3, 4}; ia3 = p; // error: can't assign an int* to an array ia3[4] = i; // ok: assign the value of i to an element in ia3
- 指针至于数组就好像iterator至于vector
- 新规范引入了std::begin和std::end来返回一个数组的begin pointer和end pointer(最后一个数组后一个位置)
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int *pbeg = begin(arr); int *pend = end(arr); while (pbeg != pend && *pbeg >=0) { cout << *pbeg << " "; ++pbeg; } cout << endl; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // $ g++ --std=c++11 13.cc // // $ ./a.out // // 0 1 2 3 4 5 6 7 8 9 // ////////////////////////////////////////////////////
- 和vector以及string不同的是, 内置数组的index的类型不是unsigned type
C-Style Character Strings
- 虽然c++支持c-style string,但是以后不应该再使用这些string, 因为它们基本就是 bug最多的来源.
- TODO
Interfacing to Older Code
- 我们是不可以通过一个array来初始化另外一个array, 但是我们可以使用一个array来
初始化一个vector, 而且在新规范引入了begin()和end()之后,变得更简单了
#include <iostream> #include <vector> using namespace std; void display(std::vector<int> someV) { for (std::vector<int>::const_iterator ite = someV.begin(); ite < someV.cend(); ++ite) { std::cout << *ite << " "; } std::cout << std::endl; } int main(int argc, char *argv[]) { int int_arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; vector<int> ivec(begin(int_arr), end(int_arr)); display(ivec); vector<int> subVec(int_arr + 1, int_arr + 4); display(subVec); } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 1 2 3 4 5 6 7 8 9 // // 1 2 3 // ////////////////////////////////////////////////////
- 由于c-style string和指针是如此的容易出错,所以新代码推荐使用vector和strings
Multidimensional Arrays
- 其实在cpp中是不存在多维数组的, 多维数组的本质就是arrays of arrays
What are commonly referred to as multidimensional arrays are actually arrays of arrays
- 对于多维数组的理解,从第一dimension开始"读取"比较容易, 比如
int ia[3][4]; // array of size 3; each element is an array of ints of size 4
- TODO
Chapter 04: Expressions
Fundamentals
Basic Concepts
- 操作符方面,cpp分为两种操作符:
- unary operators: 操作一个数字
- binary operators: 操作两个数字
- 而expression在cpp中有两种分发:
- lvalue: 可以放在assignment的左边的expression
- rvalue: 不可以放在assignment左边的expression
- lvalue和rvalue的区分非常复杂,但是从直观上来说就是:
- 我们在把一个object当做lvalue来使用的时候, 我们使用的是它的identity(location in memory)
- 我们在把一个object当做rvalue来使用的时候, 我们使用的是它的value (its content)
- 总体上来说lvalue"能力更强"
We can use an lvalue when an rvalue is rquired, but we cannot use an lvalue in place of an rvalue.
- lvalue作为rvalue使用的时候,其只需要把其内容"贡献"出来即可.
- TODO
Precedence and Associativity
- 括号的优先级最大
The Member Access Operators
- 因为"解引用"符号的优先级是比dot低的,所以我们必须在"指针引用"外面加上括号,然
后再去使用, 比如下面的使用就是错误的.
// run the size member of p, then dereference the result! *p.size() // error: p is a pointer and has not member named size
Chapter 05: Statements
Simple Statements
- 大部分的c++ statement是以分号结尾的. 所谓的expression statement就是说一个
expression会被evaluated,然后它的结果被丢弃了:
- 如下的statement是没有意义的, 因为它的结果在被丢弃前没有保存
ival + 5;
- 下面是一个在丢弃前保存的例子,不过保存的方式是打印
cout << ival;
- 如下的statement是没有意义的, 因为它的结果在被丢弃前没有保存
Null Statements
- 如果需要什么都不做的语句,那么直接一个分号就好了,注意这种情况下要加上一些
comment
while (cin >> s && s != sought) ; // null statment
Beware of Missing or Extraneous Semicolons
- 多余的分号并不是总没有危害,比如在if或者while后面跟了分号的话,可能会导致无线
循环
while (iter != save.end()); ++iter;
Compound Statements(Blocks)
- 复合语句(Compound Statement)是指的大括号括起来的语句, 也叫做块(block)
- 复合语句自己本身就是一个作用域(scope), 也就是说,在这个block中引入的名字只 能在这个block(或其子block)中使用.
- 复合语句说起来高冷,却是我们最常用的语法:对于while for等控制语句来说,后面本
来只能跟一行语句,如果需要多行语句的话,要把这个几个语句加入到一个block
while (val <= 10) { sum += val; ++val; }
Statement Scope
- 我们可以在while if等语句的control structure里面来定义变量.这样定义的变量在
只在while if的statement里面起作用.
while (int i = get_num()) // i is created and initialized on each iteration cout << i << endl; i = 0; // error: i is not accessible outside the loop
Conditional Statements
The if Statement
- if 后面跟的condition必须是能够转化成bool类型的类型
The switch Statement
- case label后面必须跟常量表达式
char ch = getVal(); int ival = 42; switch(ch) { case 3.14: // error: noninteger as case label case ival: // error: nonconstant as case label // ...
Iterative Statements
The while Statement
- while是不停执行目标statement,只要condition还为true
while (condition) statement
- 需要注意的是,我们在condition或者statement部分定义的变量,每次都会经历created 和destroyed的过程.
Traditional for Statement
- 传统的for循环要求如下:
for (initializer; condition; expression) statement
- 上述三个部分initializer, condition, expression都可以省略
- initializer里面的定义只运行一遍,但是如果变量定义于此的话,其作用域就是for语 句结束之前
Range for Statement
- c++11 引入了range for
for (declaration : expression) statement
- 这种for的特点是:
- declaration 会定义一个变量, 这个变量必须能转化为expression里面的容器的成 员变量的类型. 最简单的方法是使用auto让编译器为我们来判断.如果想更改容器里 面成员的值,那么declaration的这个变量必须是引用类型
- expression必须是一种列表,比如:
- braced initializer list
- array
- 能够支持返回iterator的begin和end的容器
- 下面是一个使用range for来把成员变量大小翻倍的方法
vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // range variable must be a reference so we can write to the elements for (auto &r : v) r *= 2
The do while Statement
- do while statement就是必须先执行一次的while循环,无论condition是否成立,需要
注意的是while后面要以分号结尾
do statement; while (condition);
Jump Statements
The break Statement
- break 会结束如下'最近'的一个语句:
- while
- do while
- for
- switch
The continue Statement
- continue会结束循环的最近一次,进入下一次,所以只能出现在循环内部
The goto Statment
- 随意跳转,最好不用.
try Blocks and Exception Handling
- exception是运行期(runtime)出现的反常行为, 比如数据库连接丢失,或者是意外输入 (unexpected input)
- 异常处理(exception handling)是指:
- 程序的一部分遇到了一个它无法解决的问题, 而且这个问题使得它无法继续运行
- 遇到问题的这部分程序只需要把问题"声明"出来,不需要知道如何解决
- 一旦"声明"出问题, 这部分程序就完成了使命.
- 异常的处理在cpp中包含如下几个部分:
- detecting: 遇到问题的部分,会使用throw expression来"声明"自己遇到的问题
- handling: 解决问题的代码会使用try后面的block来"等待"block里面的代码"声明" 问题,然后在catach里面处理
- exception class: 用来在throw和catch里面"交流"问题的类型
A throw Expression
- 遇到问题的部分使用的代码
Sales_item item1, item2; cin >> item1 >> item2; // first check that item1 and item2 represent the same book if (item1.isbn() != item2.isbn()) { throw runtime_error("Data must refer to same ISBN"); } // if we're still here, the ISBN are the same cout << item1 + item2 << endl;
- runtime_error是stdexcept header里面定义的standard library exception
The try Block
- try block是代码解决问题的部分, 其中
- try后面跟的block里面是"可能出问题"的代码, 这些代码可能调用了函数,或者函 数里面调用了其他函数,只要是从这个block里面出去的,都可以被"捕捉"到
- catch是处理exception的部分, catch可以指定只处理某种类型的exception
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int item1 = 0; int item2 = 0; while (cin >> item1 >> item2) { try { if (item1 != item2) { throw runtime_error("Data must refer to same ISBN"); } cout << "The ISBNs are the same" << endl; break; } catch (runtime_error err) { cout << err.what() << "\nTry Again? Entry y or n" << endl; char c; cin >> c; if (!cin || c == 'n') { break; // break ou of the while loop } } } return 0; } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // $ ./a.out // // 111 222 // // Data must refer to same ISBN // // Try Again? Entry y or n // // y // // 333 333 // // The ISBNs are the same // ////////////////////////////////////////////////////
Standard Exceptions
- cpp在如下的头文件里面定义了一些exception类:
- excepition header: 定义了名叫excepiton的异常
- stdexcept header: 定义了常用异常
- new header: 定义了bad_alloc 异常
- type_info header: 定义了bad_cast 异常
- exception异常, bad_alloc异常和bad_cast异常是不需要字符串做为初始化参数的,除 此之外其他的异常都需要字符串初始化参数