由于最近工作中需要使用c++编写一个工具,趁元旦三天假期学习下c++的相关特性,浏览C++ primer 5th一书,这本当时4年前买的大块头的书,看新旧程度应该只看了100页左右😓。

编译c++程序

gcc可以对c/c++代码进行编译,g++可以对c++代码进行编译,gcc和g++在编译c++代码时,在预编译 编译阶段作用相同,在链接阶段g++会自动链接标准c++库,so,而gcc需要手动加上-lstdc++
完整的编译命令如下:
gcc -g -o execute example.cpp -lstdc++

使用gcc编译完整命令:
g++ -o prog1 prog1.cc
根据使用的GNU编译器的版本,可能需要指定-std=c++0x参数来打开对C++11的支持,完整的命令如下:
g++ -std=c++0x -Wall -o prog1 prog1.cc

编译执行如下代码:

#include <iostream>

int main()
{
    return 257;
}

echo $? 输出什么内容?
总结:退出码范围0-255,超出部分处理方式对256取模。
Out of range exit values can result in unexpected exit codes. An exit value greater than 255 returns an exit code modulo 256. For example, exit 3809 gives an exit code of 225 (3809 % 256 = 225).
Exit Codes With Special Meanings

注释

习题1.8 Indicate which, if any, of the following output statements are legal:
std::cout << "/*";
std::cout << "*/";
std::cout << /* "*/" */;
std::cout << /* "*/" /* "/*" */;
After you’ve predicted what will happen, test your answers by compiling a program with each of these statements. Correct any errors you encounter.

第三条程序std::cout << /* "*/" */;存在错误,修改如下:
std::cout << /* "*/" */";
/* 注释内容*/,在/*和*/之间的内容是注释内容不会显示。

切勿混用带符号类型和无符号类型

如下程序看似简单的打印10到1的数字,其实存在死循环。

#include <iostream>

int main()
{
   for(unsigned u = 10;u>=0;--u)
   {
      std::cout<<u<<std::endl;
   }

   return 0;
}

原因在于当u减到0时,打印了0之后,--u由于u是unsigned 类型,因此将转换为全1,也就是unsigned 的最大值,检测程序如下:

#include <iostream>

int main()
{
   unsigned int u = -1;
   printf("%u\n",u);
   return 0;
}

程序输出:4294967295
在/usr/include/limits.h可以看到如下信息:

/* Maximum value an `unsigned int' can hold.  (Minimum is 0.)  */
#  define UINT_MAX      4294967295U

一种解决该问题的方法:

#include <iostream>

int main()
{
    unsigned u = 11;
    
    while(u > 0)
    {
        --u;
        std::cout<<u<<std::endl;
    }
    return 0;
}

变量声明与定义

为了支持分离式编译,C++语言将声明和定义区分开来。声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建于名字关联的实体。
变量声明规定了变量的类型和名字,在这一点上定义与之相同。除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。
如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不需要显示地初始化变量:
extern int i;//声明i,而非定义i

指针与引用的区别

定义:
the pointer is "points to" any other type.
the reference is "another name" of an object.
主要区别:
A reference is another name of an already existing object. A pointer is an object in its own right.
Once initialized, a reference remains bound to its initial object. There is no way to rebind a reference to refer to a different object. A pointer can be assigned and copied.
A reference always get the object to which the reference was initially bound. A single pointer can point to several different objects over its lifetime.
A reference must be initialized. A pointer need not be initialized at the time it is defined.

指向指针的引用

#include <iostream>

int main()
{
    int i = 42;
    int *p;
    int *&r=p;
    r=&i;
    *r=0;
    return 0;
}

上面第7行的内容,分析比较复杂的指针或者引用声明语句时,从右向左阅读有助于弄清它的真实含义。
离变量名最近的符号&对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号星号说明r引用的是一个指针,最后声明的基本数据类型部分指出r引用的是一个int指针。

const限定符

默认情况下const对象仅在文件内有效,解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:
extern const int bufSize = fcn();
//file_1.h头文件
extern const int bufSize;//与file_1.cc中定义的bufSize是同一个。

const的引用

double dval = 3.14;
const int &ri = dval;
编译器把上述代码变成了如下代码:
const int temp = dval;
const int &ri = temp;
这种情况下ri绑定了一个临时变量对象。所谓临时变量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。C++程序员们常常把临时对象简称为临时量。因此这也就解释了为什么不允许使用如下方法:

int i = 42;
const int &r1 = i;  //正确
const int &r2 = 42;//正确
int &r4 = r1 *2;//错误,r4是一个普通的非常量引用,如果允许的话修改的有可能是临时量。
指针和const

顶层const:表示指针本身是个常量。
底层const:表示指针所指的对象是一个常量。

int i = 0;
int * const p1 = &i;  //不能改变p1的值,这是一个顶层const
const int ci = 42; //不能改变ci的值,这是一个顶层const
const int *p2 = &ci; //允许改变p2的值,这是一个底层const
const int * const  p3 = p2;//靠右的const是顶层const,靠左的是底层const
const int &r = ci; //用于声明引用的const都是底层const

类型别名

有两种方法可用于定义类型别名。传统的方法是使用关键字typedef:
typedef double wages; //wages是double的同义词
typedef wages base,*p;//base是double的同义词,p是double *的同义词
c++ 11中规定了一种新方法,使用别名声明来定义类型的别名:
using SI = Sales_item;//SI 是Sales_item的同义词

指针 变量和类型别名

如果某个类型别名指代的是符合类型或常量,那么把它用到声明语句里就会产生意想不到的后果。例如下面的声明语句用到了类型pstring,它实际上是类型char *的别名:

typedef char *pstring;
const pstring cstr = 0;//cstr是指向char的常量指针
const pstring *ps;  //ps是一个指针,它的对象是指向char的常量指针

上述两条声明语句的基本数据类型都是const pstring,和过去一样,const是对给定类型的修饰。pstring实际上是指向char的指针,因此const pstring就是指向char的常量指针,而非指向常量字符的指针。
遇到一条使用了类型别名的声明语句时,人们往往会错误地尝试把类型别名替换成它本来的样子,以理解该语句的含义:

const char *cstr = 0;//是对const pstring cstr的错误理解

再强调一遍,这种理解是错误的。声明语句中用到pstring时,其基本数据类型是指针。可是用char *重写了声明语句后,数据类型就变成了char,星号成了声明符的一部分。这样改写的结果是,const char成了基本数据类型。前后两种声明含义截然不同,前者声明了一个指向char的常量指针,改写后的形式则声明了一个指向const char的指针。
stackoverflow

预处理器

使用如下功能就能有效地防止重复包含的发生:

#ifndef SALES_DATA_H
#define SALES_DATA_H
struct Sales_data
{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
#endif

标准库类型string

#include <string>
using std::string;
直接初始化和拷贝初始化

如果使用等号=初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象去。与之相反,如果不使用等号,则执行的是直接初始化。

string s5 ="hiya";//拷贝初始化
string s6("hiya"); //直接初始化
string s7(10,'c'); //直接初始化,s7内容为cccccccccc
直接初始化与拷贝初始化转换
string temp(10,'c');//temp的内容是cccccccccc
string s8=temp;//将temp拷贝给s8

上面两条语句可以合写成一条:
string s8 = string(10,'c');

标准库类型vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector容纳着其它对象,所以它也常被称为容器。

#include <vector>
using std::vector;

vector是模板而非类型,模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应把类或者函数实例化成何种类型。
因为引用不是对象,所以不存在包含引用的vector.

getline函数

getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(换行符也被读进来了),然后把所读的内容存入到那个string对象中去(不存换行符)。

迭代器介绍
//由编译器决定b和e的类型,b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(),e=v.end();//b和e类型相同

end成员负责返回指向容器尾元素的下一位置的迭代器,也就是说该迭代器指示的是容器的一个不存在的尾后元素,这样的迭代器没什么实际含义,仅是一个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器或者简称为尾迭代器。如果容器尾空,则begin和end返回的是同一个迭代器,都是尾后迭代器。

理解比较复杂的数组声明

int *ptrs[10];    //ptrs是含有10个整型指针的数组
int (*Parray)[10] = &arr;  //Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr;   //arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs;// arry是数组的引用,该数组含有10个指针

按照由内向外的顺序阅读上述语句,首先知道arry是一个引用,然后观察右边知道,arry引用的对象是一个大小为10的数组,最后观察左边知道,数组的元素类型是指向int的指针。这样arry就是一个含有10个int型指针的数组的引用。
要想理解数组声明的含义,最好的办法时从数组的名字开始按照由内向外的顺序阅读。

指针和数组

在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。

int ia[]={0,1,2,3,4,5,6,7,8,9};//ia是一个含有10个整数的数组
auto ia2(ia);//ia2是一个整型指针,指向ia的第一个元素,实际相当于auto ia2(&ia[0])

使用decltype(ia)返回的类型是由10个整数构成的数组

decltype(ia) ia3={0,1,2,3,4,5,6,7,8,9};
ia3[4] = 10;//ia3是一个数组,类型和元素个数与ia3相同

混用string对象和c风格字符串

允许使用字符串字面值来初始化string对象,例如:
string s("Hello world");//s的内容是Hello world
上述性质反过来就不成立了:无法直接将string转换为C风格字符串。可以使用如下方式:
const char *str = s.c_str();//正确
函数返回一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与那个string对象的一样。如果执行完c_str()函数后程序向一直都能使用其返回的数组,最好将该数组重新拷贝一份。

多维数组

int ia[3][4] = {
        {0,1,2,3},
        {4,5,6,7},
        {8,9,10,11}
    };

int (&row)[4] = ia[1];

上述代码把row定义成一个含有4个整数的整型数组的引用,然后将其绑定到ia的第二行。

使用范围for语句处理多维数组

int ia[3][4] = {
        {0,1,2,3},
        {4,5,6,7},
        {8,9,10,11}
    };
    
    size_t cnt = 0;
    for(auto & row:ia)
        for(auto &col:row)
        {
            col = cnt;
            ++cnt;
        }

第一个for循环遍历ia的所有元素,这些元素是大小为4的数组,因此row的类型就应该是含有4个整数的数组的引用。第二个for循环遍历那些4元素数组中的某一个,因此col的类型是整数的引用。
如果去掉for(auto & row:ia)中的引用,改成如下形式,编译报错,原因如下:
第一个循环中ia的元素实际上是大小为4的数组,如果row不是引用类型,编译器初始化row时会自动将这些数组形式的元素转换成指向该数组内首元素的指针,这样得到的row的类型就是int*,内层循环显然不合法了。

比较字符串

string s1="A string example";
string s2="A different string";
if(s1<s2) //false  s2小于s1


const char ca1[]="A string example";
const char ca2[]="A different string"
if(ca1 < ca2)//未定义,试图比较两个无关的指针