cin
,搭配输入运算符>>
使用。ostream类型的对象有:标准输出对象cout
,标准错误cerr
,日志信息clog
,搭配输出运算符<<
使用endl
为操纵符,写入endl
的效果是结束当前行,并将设备关联的缓冲区中的内容刷到设备中。缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是停留在内存中等待写入流int sum=0,value=0;
while(std::cin>>value)
sum+=value;
std::cout<<sum<<std::endl;
int a=0
;② int a={0}
;③ int a(0)
;④ int a{0}
extern
,而且不要显式初始化变量。不能在函数体内部初始化一个由extern
标记的变量extern int i; //声明
int i; //声明并定义
extern int i=0; //定义
extern
关键字const T& t
,此时常量引用不能修改绑定对象的值,但非常量引用不能绑定常量对象。指向常量的指针不能用于修改其所指对象的值const T* t
,常量指针不能改变指针本身T* const t
cosntexpr
类型以便由编译器来验证变量的值是否是一个常量表达式,声明为constexpr
的变量一定是一个常量,而且必须用常量表达式初始化string
类型等不属于字面值类型,不能被定义成constexpr
。引用和指针被定义成constexpr
时初始值受到限制,constexpr
指针和引用的初始值必须是存储与某个固定地址中对象或nullptr
(对于指针),函数体内的变量一般不存放在固定地址中(static
除外),constexpr
指针和引用不能指向这样的变量,函数体之外的对象地址固定不变,能用来初始化constexpr
指针和引用constexpr
声明中如果定义了一个指针,限定符constexpr
仅对指针有效,与指针所指向的对象无关,即constexpr
把它所定义的对象置为了顶层const
。与普通指针类似,constexpr
指针既可以指向常量也可以指向非常量(固定地址)const int* p=nullptr; //指向常量的指针
constexpr int* q=nullptr; //常量指针
typedef
或using
定义类型别名,对于包含复合类型和常量的类型别名,如typedef char* pstring
,使用const pstring cs
时,表示常量指针,而非指向常量的指针typedef double wages;
using SI=Sales_items;
auto
类型说明符,让编译器通过初始值来推断变量的类型,auto
定义的变量必须有初始值。auto
会忽略掉顶层const
,同时底层const
会保留下来,如果希望推断出的类型是一个顶层const
,需要显式指出。如果希望推断出的类型是一个引用,需要显式使用auto&
decltype
,用于选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,但不计算表达式的值。与auto
不同,decltype
会保留变量的顶层const
以及引用类型。如果表达式的内容是解引用操作,则decltype
将得到引用类型,如果decltype
的变量名加上了一对括号decltype((value))
,则表示引用类型struct
或class
,C++11可以为类的数据成员提供一个类内初始值,创建对象时,类内初始值用于初始化数据成员,没有初始值的成员将被默认初始化,类内初始值可使用花括号或等号,但不能使用圆括号os<:将s
写到输出流os
中,返回os
;② is>>s
:从is
中读取字符串赋给s
,字符串以空白分隔,返回is
,字符串会自动忽略开头的空白;③ getline(is,s)
:从is
中读取一行赋给s
,返回is
string
和vector
的size()
方法返回的都是对应的size_type
类型(string::size_type
和vector::size_type
),该类型是一个与机器无关的无符号整型
(或
)中,定义了一组标准库函数用于处理string对象中的字符,可以判断每个字符是否为数字、字母、大小写等,也可以进行大小写的转换。通过使用范围for
语句可将string对象转换成大写,或使用下标执行迭代for(auto& c:s) c=toupper(c);
for(string::size_type i=0;i!=s.size();++i) s[i]=toupper(s[i]);
vector v(n,val)
初始化了n个重复元素,每个元素的值都是val
。vector v(n)
指定了元素数量而未指定初始值,此时会对每个元素执行值初始化,对于内置类型,值初始化为0,对于非内置类型,由类执行默认初始化,若元素类型不支持默认初始化,则必须提供初始元素值。若使用花括号进行初始化,默认为列表初始化,当花括号中的类型无法执行列表初始化时,会尝试执行直接初始化(通过圆括号初始化)push_back(T)
向vector中添加元素,与C和Java不同,C++先创建一个空的vector对象,然后动态添加元素,比在创建vector对象的同时指定容量的效率更高,只有一种情况例外,就是vector中所有元素的值都一样。如果循环体内包含有向vector对象添加元素的语句,则不能使用范围for循环for(auto& i:v)
。下标运算符可用于访问vector中已存在元素,但不能用于添加元素begin()
和end()
,其中begin()
返回指向第一个元素的迭代器,end()
返回指向容器末尾元素的下一个位置的迭代器,又称作尾后迭代器,该迭代器没有实际意义,仅是一个标记,表示已经处理完容器中所有的元素,如果容器为空,begin()
和end()
返回同一个迭代器*iter
:返回迭代器所指元素的引用;② iter->mem
解引用迭代器并获取该元素的成员;③ ++iter
/--iter
:令迭代器指向容器中的上/下一个元素;④ iter1==iter2
/iter1!=iter2
:判断两个迭代器是否相等,若指向同一个元素或是同一个容器的尾后迭代器,则相等,反之不相等==
和!=
运算符,大多数没有定义<
运算符,因此使用for循环遍历迭代器时,要使用!=
做条件判断for(auto it=s.begin();it!=s.end();++it)
iterator
和const_iterator
类型,const_iterator
类似于常量指针,能读取但不能修改其所指的元素值,若迭代器对象是一个常量,则只能使用const_iterator
,若不是常量,则两者都可以使用。begin()
和end()
返回的迭代器类型由对象是否是常量决定,若想固定返回const_iterator
类型,可使用cbegin()
和cend()
==
和!=
外的更多种运算符:iter+n
,iter-n
,iter+=n
,iter-=n
,iter1-iter2
,>
,>=
,<
,<=
。迭代器之间的距离的类型是名为difference_type
的带符号整型数T a[d];
,其中a
是数组的名字,T
是数组中变量的类型,d
是数组的维度,说明了数组中元素的个数,必须大于0,数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的,因此维度必须是一个常量表达式auto
关键字由初始值的列表推断类型。和vector一样,数组的元素应为对象,因此不存在引用的数组int a[10]={}; //10个整数全部初始化为0
char a1[]={'C','+','+','\0'}; //列表初始化,显式加入空字符
char a2[]="C++"; //自动添加末尾的空字符
const char a3[3]="C++"; //错误!没有空间可存放空字符
int *a[10]; //存放整形指针的数组
int &a[10]; //错误!不存在存放引用的数组
int (*a1)[10]=&a2; //指向整型数组的指针
int (&a1)[10]=a2; //整形数组的引用
int* p=a;
等价于int* p=&a[0];
,在一些情况下数组的操作实际上是指针的操作,当数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:auto a2(a1);
,而使用decltype关键字时得到的仍是数组类型decltype(a)
int* e=&a[d];
,但这种方法极易出错,C++新标准引入了begin(a)
和end(a)
函数,可返回数组首元素的指针和尾元素的下一个位置的指针,这两个函数定义中
头文件中'\0'
结束。尽管C++支持C风格字符串,但在C++程序中尽量不要使用,因为使用不方便且易引发程序漏洞。有关两种风格字符串的转换使用见另一篇博客:C++字符串vector v(begin(a),end(a));
int a[3][4]; //大小为3的数组,每个元素是大小为4的整形数组
int (*p)[4]=a; //p指向a的第一个元素
p=&a[1]; //p指向a的第二个元素
*
- 赋值运算符
=
需要一个非常量左值作为其左侧运算对象,得到的结果也是一个左值- 取地址符
&
作用于一个左值,返回一个指向该左值对象的指针,这个指针是一个右值- 内置解引用运算符
*
、下标运算符[]
、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值- 内置类型和迭代器的递增递减运算符作用于左值对象,其前置版本所得的结果也是左值
decltype
作用于表达式时,如果该表达式的结果是一个左值,decltype
得到的结果是一个引用类型。对于类型为int*
的对象p,decltype(*p)
的结果是int&
,decltype(&p)
的结果是int**
int i=f()*g();
,其中函数运算一定会在乘法前被调用,但无法知道哪个函数会先调用。如果表达式修改了一个对象,还在其他地方使用了该对象,会引发错误并产生未定义的行为,如cout<表达式是未定义的
&&
运算符(左侧为真时才会求右侧的值)、逻辑或||
运算符,条件?:
运算符、逗号,
运算符*++iter
可以正常使用)~
:位求反;② <<
:左移;③ >>
:右移;④ &
:位与;⑤ ^
:位异或;⑥ |
:位或。位运算对象可以是带符号的,也可以时候无符号的,但如何处理符号位依赖于机器,所以一般仅将位运算符作用于无符号类型sizeof
运算符返回一条表达式或一个类型名所占的字节数,所得的值是一个size_t
类型的常量表达式,该运算符有两种使用方式:① sizeof(T);
:返回类型T
的大小;② sizeof t;
:返回对象t
所对应的类的大小。该运算符作用于数组时返回整个数组的大小,作用于string或vector时只返回固定部分的大小,不会计算内部元素所占空间unsigned int
和int
运算,int
类型的运算对象会转为unsigned int
类型,若int
型的值为负,会带来副作用。若带符号类型大于无符号类型,此时转换的结果依赖于机器decltype
关键字的参数、作为取地址符&、sizeof
、typeid
等运算符的运算对象时、通过引用来初始化数组时,上述转换不会发生;② 指针的转换:常量整数值0或字面值nullptr
能转换成任意指针类型,指向任意非常量的指针能转换成void*
,指向任意对象的指针能转换成const void*
;③ 转换成布尔类型:如果指针或算术类型的值为0,转换结果时false,否则转换结果时true;④ 转换成常量:允许将指向非常量类型的指针或引用转换成指向相应常量类型的指针或引用,而不允许相反的转换;⑤ 类类型定义的转换:类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换,while(cin>>s)
把istream
类型自动转为布尔类型,转换规则由IO库定义,若最后一次读取成功,转换得到true
,若最后一次读入不成功,转换得到false
显式转换
显式转换又称为强制类型转换(cast),分为:① 命名的强制类型转换;② 旧式的强制类型转换
一个命名的强制类型转换具有如下形式:
cast-name
(expression);
其中,type是转换的目标类型,若type是引用类型,则结果是左值;expression是要转换的值;cast-name是static_cast
、dynamic_cast
、const_cast
、reinterpret_cast
中的一种,指定了执行的是哪种转换。
double d = static_cast(j)/i;
,static_cast常用于把一个较大的算术类型赋值给较小的类型,也可用于转换编译器无法自动执行的类型转换,如找回存在于void*
指针中的值:void* p = &d; double *dp = static_cast(p);
const char *pc; char *p = const_cast(pc);
,去掉对象的const性质后,编译器就不再组织对该对象进行写操作了,但是如果对象本身是一个常量,执行写操作会产生未定义的后果。const_cast常用于有函数重载的上下文中int *ip; char *pc = reinterpret_cast(ip);
,必须牢记pc
所指的只是对象是一个int而非字符,若使用string str(pc);
就会产生运行时报错。旧式的强制类型转换:在早期版本的C++语言中,显式地进行强制类型转换包含两种形式:① 函数形式地强制类型转换:type(expr);
;② C语言风格地强制类型转换:(type)expr;
。根据所涉及地类型不同,旧式地强制类型转换分别具有与static_cast、const_cast、reinterpret_cast相似的行为。与命名的强制类型转换相比,旧式的强制类型转换从表现形式上不清晰,转换过程出问题后追踪更加困难
6.1 函数基础
static
类型可以获得生命周期贯穿函数调用及之后时间的对象,称为局部静态对象,局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。如果局部静态变量没有显式的初始值,将执行值初始化,内置类型的局部静态变量初始化为06.2 参数传递
void f(const int i)
和函数void f(int i)
本质上是一样的,不能重复定义const int ci = i;
,所以函数中有不会改变的形参时要定义成常量引用,否则该形参将不能接受const实参void f(const int*);
;② void f(const int[]);
;③ void f(const int[10]);
其中的维度表示期待数组含有多少元素,实际不一定。这三种函数是等价的,编译器处理函数调用时,只检查传入的参数是否是const int*
类型,当函数不需要对数组元素执行写操作时,数组形参应该是指向const的指针,需要改变数组元素值时才定义成指向非常量的指针initializer_list
的标准库类型;② 如果实参的类型不同,可以编写一种特殊的函数,称为可变函数模板。C++还有一种特殊的形参类型省略符,可以传递可变数量的实参,但这种方法一般只用于与C函数交互的接口程序void f(initializer_list il)
6.3 返回类型
return;
;② return expression;
;,其中无返回值的return语句只能用在返回类型是void的函数中,这类函数的最后一句后面会隐式执行returnusing arr = int[10]; arr* f(int i);
;② 使用尾置返回类型:auto f(int i) -> int(*)[10];
;③ 使用decltype:int arr[]={1,2,3,4,5}; decltype(arr) *f(int i)
,要注意decltype并不负责把数组类型转换成对应的指针6.4 函数重载
void f(int* const);
,底层const:void f(const int*)
,底层const重载函数也可以接受非常量对象,但是会优先选用非常量版本的函数6.5 特殊用途语言特性
inline
即可将其声明为内联函数,定义在类内部的函数是隐式的inline函数。一般内联机制用于规模较小、流程直接、频繁调用的函数,很多编译器都不支持内联递归函数assert
和NDEBUG
。① assert是一种预处理宏,定义在cassert
头文件中,使用一个表达式作为它的条件assert(expr);
,首先对expr求值,如果表达式为假(0),assert输出信息并终止程序执行,如果表达式为真(非0)就什么也不做;② NDEBUG预处理变量:assert的行为依赖于一个名为NDEBUG的预处理变量的状态,如果定义了NDEBUG,则assert什么也不做,默认状态下没有定义NDEBUG,此时assert执行运行时检查,可以在main.c文件的开始写入#define NDEBUG
来关闭调式状态,也可以通过命令行定义6.6 函数匹配
- 精确匹配,包括:① 实参类型与形参类型相同;② 实参从数组类型或函数类型转换成对应的指针类型;③ 向实参添加顶层const或删除顶层const
- 通过const转换实现的匹配
- 通过类型提升实现的匹配
- 通过算术类型转换或指针转换实现的匹配
- 通过类类型转换实现的匹配
6.7 函数指针
bool (*pf)(const string &);
,其中指针两端的括号不可少,如果没有括号,则是声明了一个返回值为bool指针的函数pf = func;
和pf = &func;
等价。可以直接使用指向函数的指针调用改函数,无需提前解引用,bool b = pf("hello");
和bool b = (*pf)("hello");
和bool b = func("hello");
等价。不同函数类型的指针间不能相互转换,可以将函数指针赋值为nullptr或0,表示指针没有指向任何一个函数void f(bool pf(const string &));
和void f(bool (*pf)(const string &));
等价,可以直接把函数名作为实参使用,会自动转换成指针。同样,虽然不能返回一个函数,但是能返回指向函数类型的指针,将decltype作用于某个函数时,它返回函数类型而非指针类型7.1 定义抽象数据类型
Person *const
,尽管this是隐式的,但它仍然需要遵循初始化规则,意味着默认情况下不能this绑定到一个常量对象上,也使得不能在一个常量对象上调用普通的成员函数。C++允许把const关键字放在成员函数的参数列表之后string func() const;
,此时的const表示this是一个指向常量的指针,这样的成员函数被称为常量成员函数,常量对象、常量对象的引用或指针都只能调用常量成员函数string Person::func() const{}
Person() = default;
Person(const string &s):name(s){}
,构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同,如果不能使用类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员7.2 访问控制与封装
friend
关键字开始的函数声明语句即可,最好在类定义开始或结束前的位置集中声明友元。友元的声明仅仅指定了访问权限,如果要调用友元函数,必须在友元声明之外再专门对函数进行一次声明,为了使友元对类的用户可见,通常把友元函数的声明与类本身放置在同一个头文件中7.3 类的其他特性
mutable
关键字做到这一点=
的初始化形式,或使用花括号括起来的直接初始化形式*this
,它是将类对象的引用作为左值返回,若const成员函数返回*this
,它返回的类型是常量引用,通过区分成员函数是否是const,可以对其进行重载Person &get(){return *this;} const Person &get() const{return *this;}
,在某个对象上调用get()时,该对象是否是const决定了该调用哪个版本class
或struct
后面,Person p;
和class Person p;
两种声明等价class Person;
,这种声明被称为前向声明,对于类型Person来说,在它声明后定义前是一个不完全类型,不完全类型无法创建对象,也无法访问其成员friend class Animal
,可以把其他类的成员函数定义成友元friend void Animal::eat(int);
,如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员,想令某个成员函数作为友元,必须仔细组织程序的结构以满足声明和定义的彼此依赖关系:① 首先定义Animal类,其中声明eat函数,但是不能定义它。在eat使用Person成员之前必须先声明Person;② 接下来定义Person,包括对eat的友元声明;③ 最后定义eat,此时它才可以使用Person的成员void Person::eat(){}
Person():Person("",0,0){}
T()
的表达式显式地请求初始化时explicit
可以阻止隐式转换,explicit关键字只对一个实参的构造函数有效,且只能在类内声明时使用struct Person{int age; string name;};
,可以使用一个花括号括起来的成员初始值列表来初始化聚合类的数据成员,如果初始值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化static
关键字声明静态成员,该关键字只出现在类内部,在类外部定义静态成员时,不能重复static。由于静态成员不属于类的任何一个对象,所以只能在类的外部定义和初始化静态成员,一个静态数据成员只能定义一次