第一部分:基础
开始
-
main函数的返回值必须是整型
-
访问main的返回值的方法依赖于系统。在UNIX和Windows系统中,执行完一个程序后,都可以通过echo命令获得其返回值。
-
微软编译器编译:命令cl调用编译器,/EHsc是编译器选项,用来打开标准异常处理
-
endl:这是一个被称为操纵符(manipulator)的特殊值。写入endl的效果是结束当前行,并将与设备关联的缓冲区(buffer)中的内容真正写入设备中。缓冲刷新操作可以保证到目前为,止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。
-
调试注意事项:用cout输出调试内容时候应该写endl,防止程序发生错误时候,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。
-
采用文件重定向方法读取标准输入,输出标准输出
$ addItems <infile>outfile
addItems是可执行文件。 -
字符类型包括char,signchar,unsignchar,这三种两两之间均不一样。实际上存储只有两种,带符号和不带符号,char由编译器决定用signchar还是unsignchar。
-
类型选择:
- 明确知道不是负的时候选择无符号数
- 算数表达式中不要使用char和bool,因为char在一些机器上是有符号的但在另一些机器上可能是无符号的
- 如果要用一个不大的整数,明确指定他的类型是signed char或者unsigned char
- 浮点数一般用double。
变量和基本类型
基本内置类型
- 当我们赋给带符号类型一个超过他表示范围的值时,结果是未定义的。
- 程序应该尽量避免依赖于实现环境,如果我们把int看作不变的4个字节,那么程序将是不可移植的。
- 0开头的数字代表八进制,0x开头的数字代表16进制。
- 默认字面值数字是带符号的,可以用u后缀表示无符号。L后缀表示Long,同理f,LL
- \x后面跟1个或多个16进制数字表示字符,或者\后面跟3个八进制数字,每个数字部分表示字符字面值。
变量
- 数据类型决定变量的存储空间大小,布局方式和该空间能存储的值的范围
- c++11的标准中,采用花括号来初始化变量地打了全面应用(列表初始化)。如果采用列表初始化,当存在初始值有溢出的风险时,编译器会报错。
- 内置变量在函数体外定义,被初始化为0,在函数体内部定义不会被初始化(未定义的)
- 类的对象如果没有显示初始化(在函数体内部),其值由类决定。
- extern声明一个外部变量(不在当前文件)
- 标识符,下划线和字母开头,下划线,字母和数字组成。c++11中不能连续出现两个下划线,不能以下划线和大写字母开头,定义在函数体外的标识符不能以下划线开头。
复合类型
-
引用一旦初始化,就无法更改绑定的对象。
-
引用类型(和指针)要和它绑定的对象严格匹配,非常量引用不能与字面值或者某个表达式的计算结果绑定在一起。由两个例外:
-
初始化常量引用时,可以用任意表达式作为初始值(并非常量的结果),该表达式可以转换为引用的类型即可。也就是说const的引用可以引用非const对象,此时不允许通过引用改变变量的值。
int i=53; const int &ci=i; //实际上执行了下面的操作 int i=53; const int temp=i const int &ci=temp;
-
指针也同理,允许一个指向常量的指针,指向一个变量,此时无法通过指针更改变量的值
-
-
指针无需在定义时初始化。
-
*是解引用符号,得到指针指向的对象的地址,&取值符号。
-
c++11后建议用nullptr来齿梳化指针,它是一种特殊类型的字面值,可以被转换为任意其他指针类型。
-
指针初始化可以用字面值0,但是不能把一个等于0的int类型赋值给指针作为初始值。
-
void*指针是一个特殊的类型,可以用于存放任意对象的地址。可以比较,作为函数的输入和输出,赋值给两一个void指针。但是不能直接操作void指针所指向的对象,因为我们并不知道这个对象的类型,也就无法确认这个对象所能执行的操作。
Const 限定符
- const对象一旦创建就无法改变,所以const对象必须初始化。
- const的常量特征仅仅在执行改变时候才会发挥作用(const常量可以赋值给其他变量)
- 默认情况const对象仅在文件内有效。为了确保const常量可以在任何文件使用,我们在定义和声明的时候都加上extern关键字,这样定义一次就可以了,后续使用的时候只需要要声明即可。
- const指针,即*const定义的指针,此时指针的值无法指向其他变量或常量(和引用一样)
- 顶层const表示指针本身是一个常量,底层const表示指针所指的对象是一个常量
- 对象拷贝的时候,必须具有相同的底层const,或者两个对象的数据类型必须能够转换(变量可以转换为常量)
- constexpr int mf=20由编译器确定变量的值是否是一个常量表达式,constexpr定义的变量一定是一个常量,必须用常量表达式来初始化
- constexpr定义的指针是顶层指针
类型处理
- typedef int i给int起了个别名叫做i。c++11中可以用using i=int来起别名。
- auto可以让编译器替我们分析表达式所属的类型,必须具有初始值。auto声明的一条语句中计算的类型结果必须一致。
- auto一般忽略顶层const保留底层const
- const auto明确auto推断顶层const
- auto类型的引用顶层const会保留。
- decltype用与推断类型decltype(fun()) sum=x;不会真的执行fun()。而是返回fun的返回值类型(在函数声明中可以直接得到)
- decltype用的表达式如果是一个变量,decltype返回该变量的类型(包括顶层const和引用在内)
- 值得注意的是,引用从来都是作为所指对象的同义词出现,而在decltype出是例外的,decltype(cj)返回的是一个常量int的引用,而不是常量int(cj是一个常量int的引用)。
- 如果decltype中表达式的内容是一个解引用(*p),返回的并不是指针所指向的对象本身,而是一个引用。
- decltype((variable))的结果永远是引用。
预处理器
- #define SALES_DATA_H,定义一个预处理变量,
- 头文件保护符,依赖于预处理变量
#ifndef SALES_DATA_H
#define SALES_DATA_H //头文件保护符必须唯一,通常用全大写的文件名
#include <string>
//....
#endif
字符串、向量和数组
string
getlines(cin,s); //标准输入中读取一行读取的时候读取换行符,但是不会把换行符写入string对象
string::size_type; //该类型是size()的返回值类型,同时也是string对象下表索引中所需的类型(int会转换为该类型),该类型是unsigned
string s="vec";//s是字符串字面值的副本,不包括字符串最后的空字符
//cctype头文件中的一些函数
isalnum(c);//当c是字母或数字时为真没
isalpha(c);//c是字母为真
isdigit(c);//c是数字为真
ispunct(c);//c是标点为真
isspace(c);//c是空格为真
isupper(c);//
tolower(c);//
toupper(c);//
- 使用=初始化一个变量实际上执行的是拷贝初始化,不使用等号执行的是直接初始化
- 如果一条表达式中有size()应该避免使用int,混用int和unsigned可能会发生问题。
- string对象和字符字面值和字符串字面值在一条语句中使用时,必须确保+号两侧的运算对象至少有一个是string对象。
- 字符穿字面值与string是不同的类型。
- 如果要改变sring对象的字符,需要把范围for语句中的变量定义成引用类型。
- 如果string对象的字符过大,使用范围for循环时,即使不改变string的值也声明称引用来避免拷贝
Vector
- vector表示对象的集合,所有对象的类型都要相同
- vector为显示初始化的对象会被默认初始化(包括内置对象,比如int,无论vector定义在函数体外还是函数体内,都会被默认初始化为0)。
- 注意vectro(“asdf”)是错误的初始化方法,不能用字符串字面值构建vector对象,可以用花括号,此时采用列表初始化的形式。可以使用字符串字面值初始化string
- push_back向vector中高效的添加元素,一般而言只有当所有的元素值都一样的时候,可以在初始化容器的时候给出值,否则定义一个空的vector对象,再添加值更高效。
- 注意:如果循环体内部包含向vector对象添加元素的语句,不能用范围for循环。范围for循环内不能随便更改比所遍历序列的大小。
- vector.size()返回的是vector::size_type类型。
- 不可以用下标向vector中添加元素。
迭代器
- string也支持迭代器,迭代器提供了对对象的间接访问。
- 获取迭代器需要通过有迭代器的类型的返回迭代器的成员函数。vec.begin(),vec.end(),指向的是最后一个元素的下一个位置,begin和end返回同一个值可以判断vec为空(string同理)。如果不需要通过迭代器修改对象的值,可以用vec.cbegin(),无论vector对象本身是否是常量返回的都是const_iterator类型(vector::const_iterator)
- 获取迭代器所指元素的值需要用到解引用符号,item->mem等价于(*item).mem
- 只有一些标准库类型有下标运算符,因此尽量避免,采用迭代器的方式。所有的标准容器的迭代器都提供了==和!=,但他们中的大多数都没定义<运算符。
- 因此在string和vector中,我们可以使用下标运算符,同时他们的迭代器也可以使用iter+n之类的操作,但是其他标准容器并不一定支持这种操作。但是所有的容器都支持迭代器的递增。
- 注意对容器对象的容量进行操作,可能使该容器对象的迭代器失效
- string和vector迭代器支持迭代器-迭代器,返回的是一个有符号的类型(difference_type)
数组
- 数组的维度必须是一个常量表达式,不能是变量。
- 和内置类型一样,如果在函数内部定义了某种类型的数组,默认初始化会令数组中含有未定义的值
- char a3[]=“asd”,自动添加字符串结束的空字符,需要保证数组的大小符合条件,
- **注意:**数组不允许拷贝赋值。不能拷贝初始化,也不能把一个数组直接赋值给另一个数组。
- int *ptr[10];大小为10的数组,数组内部是指向int的指针。注意:不存在引用的数组
- int (*ptr)[10]==&attr,ptr指向一个函数10个int类型的数组,引用同理。注意引用不需要取址符号
- int *(&ptr)[10]==attr,ptr是一个引用,之后把()去掉再看,这就是一个包含10个指向int的指针的数组,因此这是对包含10个指向int指针的数组的引用
- 数组下标通常会被定义为size_t类型,是带符号的,因此可以有负下标。而vector和string的下标必须是无符号类型。
- 在很多用到数组名字的地方,编译器都会自动转换为指向该数组首元素的指针。在&,sizeof,typeid,decltype时,数组不会被转换为指针
- auto的推断数组得到的类型是指针,decltype推断数组得到的是数组。
- c++11中引入了两个begin()和end()函数用于获取指向数组内元素的指针(可以和stringvector的迭代器类似使用)
- begin(attr)-end(attr)的返回值是ptrdiff_t的标准库类型是带符号的
- 使用范围for循环访问多维数组,需要确保除了内循外的所有地方都是引用类型。
c风格的字符串
- strlen§
- strcmp(p1,p2)
- strcat(p1,p2)
- strcopy(p1,p2)
- 传入上述函数的指针必须指向以空字符串作为结束的数组。(注意列表初始化字符素组的时候需要显示给出空字符,拷贝赋值不需要)
- 如果对字符数组使用《实际比较的是指针,这是无意义的。(这个指针是const char* 指针,底层const,无法通过指针更改对象的值,char *const 是顶层const,无法改变指针的值,可以通过指针修改所指对象的值)
- 对于string类型,可以用字符串字面值初始化,更一般的请开给你是,任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代。相反无法用string来初始化字符数组,和char*指针。可以用s.c_str()返回c风格的字符串,结果是一个指针(const char*),该指针指向一个以空字符结束的字符数组。无法确保c_str()返回的值一致有效,如果后续更改了s的值,可能会让之前返回的数组失去作用。
- 允许使用数组来初始化vector对象,需要知名拷贝区域的首元素地址和未元素地址 vector vec(begin(int_attr),end(int_attr)),或者vec(&int_attr[0],&int_attr[10])
表达式
- 当一个对象被作用于右值时,用的是对象的内容。左值用的是对象的身份。
- 需要用右值的地方可以用左值来代替,但是不能把右值当作左值。
- 取值符号作用与左值对象,返回的是右值。内置解引用符,下标运算符,迭代器解引用符,string和vector的下标运算符结果都是左值。
- decltype作用与返回左值的表达式得到的是一个引用。
- 运算对象的求值顺序和优先级无关,如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个对象。但是当改变运算对象的子表达式本身又是另一个子表达式的运算对象时,可以这么做,比如*++iter,此时,递增运算先求值带能解引用
算数运算符
- 算术运算符的对象和求值结果都是右值。
- 一元算数运算符作用域指针或者算数值时,返回的是运算对象(提升后)的副本。
- ( m / n ) ∗ n + m (m/n)*n+m%n=m (m/n)∗n+m,因此如果m是正,n是负,余数和m同号(正)
逻辑关系运算符
- 运算对象和结果都是右值
- 比较运算的时候除非比较对象是bool类型,否则不要使用布尔字面值true和false。
赋值运算符
- 对于类类型来说,初始值列表可以是空,此时编译器创建一个初始化的临时变量将其赋值给左侧运算对象(类定义这个临时变量的初始值)。
- a=a+b和a+=b的结果一致,但是前者求值两次。在a和b很大的时候尽量使用符合运算符。
递增和递减
- ++a和a++,前者返回对象本身(左值),厚泽把原始对象的副本作为右值返回。后置版本需要将原始值存储下来,如果我们不需要求改签的值,尽量使用前置递增。
- *ptr++等价于*ptr,之后在做ptr++。后置运算符的优先级高于解引用运算符。
成员访问运算符
- ptr->mem等价于(*ptr).mem()
条件运算符
- ?:,条件运算符的优先级非常低,因此当一条表达式中嵌套了条件运算符,需要在条件运算符两侧加括号。
位运算符
- 一般来说运算对象是小整形,会提升。对于有符号数位运算如何处理符号位依赖于机器,此时左移可能会改变符号位的值,因此这是一种未定义行为。
- 强烈建议位运算仅作用域无符号类型。
sizeof运算符
- 返回表达式或一个类型名字所占的字节数。
- c++11可以允许使用作用域运算符来获取类成员的大小,sizeof无序提供具体对象。
- sizeof计算数组得到的是数组所占空间的大小,并不会把数组当作指针来使用
- 对string和vector对象执行sizeof运算返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
类型转换
- 运算类型包括无符号和有符号,如果无符号类型大于等于有符号类型,转换有符号到无符号。如果无符号类型小于有符号类型,如果无符号数的所有值都可以用有符号数表述,则无符号转有符号,否则有符号转无符号。
- decltype作用与数,数组不会被转换为指针。
- nullptr可以转换为任意指针类型,指向任意非常亮的指针能转换为void*类型
- 使用strtic_cast(val),可以把val强制转换为type类型。我们可以使用该方法找回void指针的具体类型。
- const_cast(val),只能转换const类型==(只能改变表达式的常量属性)(并且只能由它改变常量属性)==,一般谨慎使用,我们用这种类型转换去掉const后,编译器不再阻止我们进行写操作,但是如果原对象本身是const的,强制写会导致未定义。在函数部分,我们将介绍const_cast的一种安全使用场景
- reinterpret_cast(val),简单理解为自欺欺人的改变,并没有实际改变类型,比如我们把指向int的指针转变为字符类型,我们需要记住该指针依旧是int指针,如果当作char来给string初始化,就会发生错误。
- 旧式强制类型转换(type)expr,type(expr)。
语句
- switch语句中,在case中尽量不要定义并初始化变量,如果一定要这么做请用{}
- try,throw,catch
- 我们只能默认初始化exception,bad_alloc,bad_cast对象。其他异常应该使用string或c风格字符串初始化异常类,但是不允许使用默认的初始化方法。
try{
...;
throw runtime_error("safasdf");
}catch (runtime_error err){
couterr.what();//返回初始化一个具体异常类型时的初始化字符串(const char*)。每个标准库异常对定义了what,返回值是一个c风格的字符串
}
函数
- 函数执行第一步是(隐式)定义并初始化形参,实参是形参的初始值。
- 函数最外层作用域中的局部变量也不能使用与函数形参一样的名字
- 函数的返回值类型不能是数组,可以是指向数组的指针。
- 我们把定义域执行期间内的对象成为自动对象,块执行结束后,块中创建的自动对象的值就变成了未定义。
- 局部静态对象static int a=0,把a变量的生命周期提升至贯穿整个程序。只有在执行路径第一次经过该对象的定义语句时才会初始化该对象。
- 如果静态对象没有被显示初始化,它将和vector类似,执行值初始化,内置类型德局部局部静态变量会被初始化为0
- 函数应该在源文件中定义并在头文件声明,我们调用函数时,需要导入头文件。
- c++支持分离式编译,我们只需要编译修改的源文件即可,之后把对象文件链接在一起。(省去了未修改源文件的编译过程)、
- 我们无法在声明中修改已经定义了的默认实参,局部变量不能作为默认实参,表达式可以作为默认实参。
参数传递
- 当指针执行拷贝的时候,拷贝的是指针的值,两个指针不相同,但指向相同的对象
- 当函数无序修改引用形参时,最好使用常量引用
- 当用实参初始化形参时,会忽略实参的顶层const。当形参由顶层const时,传递给他常量对象和非常亮对象都可以。因此重载的时候忽略顶层const。
- 如果形参是引用类型(非常量),则实参不能是字面值,表达式,需要转换的对象或者const类型的对象。
- 函数不需要修改实参时,应该使用常量引用。如果fun1使用const引用,而fun1中调用的fun2使用非常量引用,那么fun1中的常量就无法传递给fun2中的非常量!!
- 数组形参实际上传递的是指针,const int*==const int[]==const int[10]。当函数不需要对数组元素进行写操作时,数组形参应该是执行const的指针。
- 数组引用形参和指针类似,把*改成&
- 传递多维数组:int (*matrix)[10] 此时10不可以省略,二维数组的最后一维不可以省略。形参应该是指向数组的指针,用一次解引用得到的数组的地址,在用一次解引用得到的才是数组的第一个元素。
- int main(int argc,char** argv)。argc表示传递参数的个数,argv的第一个字符串是文件名字,最后一个字符串是0
- 使用initializer_list来传递可变形参。该对象中的元素永远都是常量值。我们可以通过fun({})的形式传递实参。
返回
- 不要返回局部对象的引用或指针。
- 调用一个返回引用的函数得到的是左值,可以赋值,get_value(s,0)=‘A’,这是合法的语句,
- c++11规定函数可以返回花括号包围的值。返回类型为vector
- mian可以不写return,编译器自动添加一条return 0
- 数组不能拷贝,函数无法返回数组,可以返回指向数组的指针 int (*fun(inti))[]。可以用->简写成尾至返回类型的形式。 fun(int i)-> int(*)[]。此外还可以使用decltype(odd) *fun(int i),odd是一个数组。
函数重载
- const_cast和重载,我们实际上执行的函数形参都是用const,但是可以根据实参传递是否是const来重载函数,如果不是const返回的引用应该是非const引用。
const string &fun(const string &s1,const string &s2){
return;
}
string &fun(string &s1,string &s2){
auto &r=fun(const_cast<const string&>(s1),const_cast<const string&>(s2));
return const_cast<string&>(r)
}
内联函数和constexpr函数
- 在函数前加上inline关键字,此时调用该函数就会在调用函数内部展开。
- constexpr函数能用于常量表达式的函数,我们约定:函数的返回类型和所有的形参类型都是字面值类型,而且函数体内部有且只有一条return语句(可以有其他实际不执行操作的语句),constexpr实际上被隐式的指定为内联函数。
- 我们允许constexpr函数返回值是一个非常量,此时返回值和实参有关,实参是常量表达式,返回值也是常量表达式。
- 内联函数和constexpr函数可以在程序中多次定义,一般定义在头文件中。
函数指针
-
函数指针指向的时函数,函数指针的类型由函数的返回类型和形参类型共同决定。
bool length(const string &) 则函数指针 bool (*pf)(const string&)
,可以看到函数指针只需要把函数名字替换成(*ptr)即可。 -
我们把函数名当作一个值使用,该函数会自动转换为指向函数的指针。
-
我们可以直接使用指向函数的指针调用该函数,而无需*符号。
-
我们要定义一个指向重载函数的指针,则上下文必须清晰的描述到底是用哪个函数,
-
函数指针形参,传递的时候可以直接传递函数名,形参定义的时候同指针的定义一样,也可以不定义成指针的形式,而是定义成函数的形式,此时会自动转换为指针。我们也可以使用typedef来简化编码。下面lengthComapare是函数,使用typedef的形式定义了函数别名和函数指针别名
bool lengthCompare(const string&); typedef bool Func(const string&); typedef decltype(lengthCompare) Func2; typedef bool (*FuncP)(const string&); typedef decltype(lengthCompare) *FuncP2;
-
返回指向函数的指针,一般采用类型别名和尾置表达式的形式,直接写太复杂了
int (*f1(int))(int*,int)
f1是函数,返回的是一个指向函数的指针,所指向的函数返回类型为int,形参类型为int*和int。尾置形式int f1(int)->int (*)(int *,int)
。同理可以和数组类似,我们用decltype来提前计算函数指针所指向的函数类型decltype(sumLength) *getFcn(const string &)
。此时decltype返回的是函数类型,我们需要显示添加*来表示返回值是个指向函数的指针。