Table of Contents
6.2.3.1 Signed adn Unsigned Characters
6.3.6.1 The auto Type Specifier
6.3.6.3 The decltype() Specifier
6.1.1 Implementations
C++ 的实现可以是hosted或者freestanding. hosted实现所有标准中描述的标准库. freestanding可以提供更少的标准库,只要<cstddef>, <cfloat>, <limits>, <climits>, <cstdint>, <cstdlib>, <new>, <typeinfo>, <exception>, <initializer_list>, <cstdalign>, <cstdarg>, <cstdtool>, <type_traits>, <atomic> 提供了就行.
6.2 Types
所有name(名称)都要有一个type(类型)与它对应. type决定了可以对name实行什么样操作.
6.2.1 Fundamental Types
integeral types: boolean, character, integer.
arithmetic types: integral and floating-point.
user-defined types: Enumerations and classes.
built-in types: fundamental types, pointers, references.
6.2.2 Booleans
根据定义,当转换为整数时, true为1,false为0. 反过来,当整数转换(可以隐式转换)为bool时; 非零整数转换为true; 0 转换为false.
用{}-initializer 可以来阻止narrowing:
bool b1 = 7; // true
bool b2{7}; // error
bool b3{7!=0} // true
6.2.3 Character Types
char, signed char, unsigned char, wchar_t(大小是implementation defined), char16_t(16bit), char32_t(32t).
char是否有符号是implementation defined. 只要char表示字符的数值在0-127之间,就没什么大问题.
6.2.3.1 Signed adn Unsigned Characters
signed 和 unsigned还有char之间的指针不能隐式转换.
6.2.3.2 Character Literals
enclosed by ''
'\x05f'
L'a' 是wchar_t类型
U'' 4byte的字符
u'' 2byte的字符
6.2.8 Sizes
C++中一个对象的大小是用char的大小来表示的. 根据定义,char的大小为1.
1 = sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
1 <= sizeof(bool) <= sizeof(long)
sizeof(char)<= sizeof(wchar_t) <= sizeof(long)
sizeof(float) <= sizeof(double) <= sizeof(long double)
char至少有8bits (typically 8 bits), short至少16bits, long至少32bits.
如果需要指定的大小的int, <cstdint> 里面包含了各种大小的int.
6.2.9 Alignment
alignof()返回表达式的alignment的要求
6.3 Declarations
在一个name(identifier)被使用之前,它必须被声明. 有很多declaration也同时是definition. 一个definition是一个declaration同时提供了程序该如何使用的信息.
在global scope中
char ch;
auto count =1;
const char* name ="XXX";
struct Date{int d, m, y;};
int foo(){return 2}
//上面的是definition, 设定了内存空间
using Point = std::complex<short>;
//上面这个也是definition
//下面这些只是declaration
double sqrt(double);
extern int error_number;
struct User;
可以有多个声明但是只能有一个定义
ps:我之前以为全局中的,比如 int x,算作定义,局部变量中的int x只能算声明,但是想想应该不是这样的. 首先如果有类A, 局部变量 A x;的话x在声明后就调用constructor初始化了,所以算作definition. 同样 int x; 也应该被算作初始化了,只不过没有指定值,所以应该也算definition.
6.3.1 declaration 的结构
可选的prefix specifier(static, virtual, extern) base type(const int, vector<float>) declarator(可选是否包含名字, p[7], n, *(*)[]) 可选的suffix function specifiers(const, noexcept) 可选的initializer或者function body(={7,5,5} 或者 { return 233;}).
declarator 包含了 一个可选的名字name, 和一些declarator operator.
prefix operator: *(pointer), *const (constant pointer), * volatile(volatile pointer), & (lvalue reference), && (rvalue reference), auto
postfix operator: [], (), ->
postfix比prefix绑定的更紧. 比如 char *kings[]. kings是数组还是指针呢? 因为postfix绑定的更紧,所以应该先读[], 所以kings是数组,数组的元素类型是 char*. char (*kings)[], 这个用了括号,先读括号内的, 所以kings是指针, 指向一个char的数组
6.3.4 Scope
scope就是一个name可以被使用的区域了.
Local scope:在函数或者lambda里声明的名字叫做local name, 它的scope从声明开始到所属的block结束.函数参数的被认为是local name,scope是函数最外层的block.
Class scope: 在一个class中但是在任何函数,其他class,enum class, 其他名称空间外声明的名字叫做member name, scope从class的声明{ 到结束}
Namespace scope:在一个名称空间内但是在任何函数,lambda,class, enum class, 其他名称空间外声明的name 叫做namespace member name. scope从declaration开始到他所属的namespace 结束. 这个name也可以被其他的translation unit取用.
Global scope:在所有namespace, class, enum class, namespace, ... 之外声明的name叫做global name. scope从声明开始到所属的文件结束.也可以被其他文件取用.
Statement scope: 在for-, while-, if-, switch- 的 ()中定义的. scope从声明开始到语句结束. 也是local name.
Function scope:从声明到函数结束.
在一个block(包括statement)里声明的name可以隐藏它所在的block之外的相同的name. 如果一个global name被隐藏了,可以通过::前缀来取用.比如
int x;
void foo()
{
int x= 1;
::x = 2;//global x
x=2;
}
非class成员的名字的声明开始指的是在declarator 之后,initializer之前. 比如
int x =99;
void foo()
{
int x= x;//在 int x 的时候就声明了local name x, 等号后面的x此时指的是local name x而不是global x
}
6.3.5 Initialization
X a1{v};
X a2={v};
X a3= v;
X a4(v);
第一种是C++11新加的list initialization, 基本上可以用于任何情况. 用{}可以防止narrowing.
如果用name{}的话就是默认值.
但是有些情况,比如 auto a1{99}; 这种情况 auto会变成initializer_list<int>,可能不是我们想要的,所以用auto的时候偏向于使用 =. 另一种情况,vector<int> v1{99}, 这种被解释为vector里初始化添加一个99, vector<int> v1(99) 调用构造函数,初始化99个int在vector中. 产生这种情况的原因是vector 有接收initializer_list的构造函数,如果没有的话v1{99}就会被解释为调用接收一个int的构造函数.
6.3.5.1 Missing initializers
如果global,namespace, local static, static member没有指定initializer的话, 会自动用{} 等价的初始化.
在堆里创造的变量默认没有初始化,除非是用户定义的有构造函数的类型. 如果要指定的话可以用{}, 比如 new int{10}.
在stack上的,自动变量,没有指定initializer, 没有well-defined 值.
基本上指定initializer的情况就是弄一个buffer. 比如 char buffer[4096];. 这个buffer也可以初始化 char buffer[4096]{}, 这样所有元素都是0, 这种初始化会话费时间,所以要考虑一下到底需不需要.
6.3.6.1 The auto Type Specifier
如果一个变量的声明有initializer, 可以使用auto来使编译器自动推导类型
可以用const, & 等来修饰auto.
注意: 一个表达式的类型不可能是引用, reference在expression里被隐式地dereference了.
void g(int& v)
{
auto x = v; // 表达式"v"的类型是int 而不是 int&
auto& y = v; // y的类型是int&
}
6.3.6.3 The decltype() Specifier
decltype(expr) 反映expr的type,这个type是编译器已经知道的类型.所以这个是编译期决定的,所以expr不会在运行时被evaluate一遍.
6.4 Objects and Values
lvalue指的是 在内存中被分配了空间 的 object. rvalue指的是不是lvalue的东西, 比如一些临时值,函数返回值. 没有移动语义之前 lvalue指的是那种可以放在等号左边的东西,rvalue就是不能放在等号左边的东西. 注意这里我说的等号是指默认的等号操作符,如果你overload自己的=操作符,那么用这个被你overload的=操作符的rvalue是可以放在等号左边的.
PS:个人的想法. temporary value实际上肯定会在内存中分配空间的, 但是关键是这种value生命周期太短了,只是用于一瞬间的运算或者移动. 有人会说那函数返回值可以绑定到一个变量上啊比如 int x = foo(); 注意, foo()返回的值是rvalue, 这个rvalue用来初始化x而不是绑定到x上,所以x和foo()在理论上不是同一个物体. 另外有些人也可能会说 咱们可以用const lvalue reference 或者rvalue reference来绑定rvalue呀,比如 bar(int&& x)或者bar(const int& x); 这样rvalue不就有名字x了吗, 有名字不就是lvalue了吗. 没错,确实x,这个rvalue reference 延长了rvalue的生命周期,使得你可以对rvalue做一些lvalue才能做的操作.等bar返回了以后,x引用的rvalue还是会死. 为什么这么麻烦? 因为傻逼,没有什么为什么,给编译器实现者参考的,程序员只用了解下rvalue,lvalue,移动语义这些就行了. 其实不止l,rvalue, 他们还有xvalue, prvalue, glvalue. std::move()实际上是把lvalue变成xvalue,而不是rvalue, 算了,傻逼,别多想了,越想越麻烦.
6.4.2 Lifetime of Objects
Automatic:从定义开始,离开scope结束.
Static: 直到程序结束.
Free store: new开始 delete结束
Temporary objects:临时的物体,就刚刚讲的,如果有引用来引用它,它的生命周期就是引用的声明周期. 如果没有引用,那它在包含它的一整串expression结束后死掉.
Thread local:
6.5 Type Aliases
using Pchar = char*; //pointer
using PF = int (*) (double); //pointer to a function
template<typename T>
class sb{
using value_type = T;
};
template<typename T>
using Vector = std::vector<T,My_allocator<T>>;
//typedef
typedef int int32_t; // == using int32_t = int;
typedef void (*foo)(int); // == using foo = void (*)(int);