使用初始化列表初始化内置类型的时候,编译器会做更加严格的检查。
按书上的说法j
和k
都会有编译错误。而i
可能不会有编译错误,虽然现在大多数的编译器在i
的时候都会报错。所以个人觉得对于内置类型也使用初始化列表来初始化不是个强制要求,因为代码看起来不舒服,还要多打几个字母。
int i = 1.1;
int j{ 1.1 };
int k = { 1.1 };
任何显式的初始化都会把声明变成定义。
extern int g_i = 0;
这是个定义。
声明其实也可以写在函数内。
比如在调试程序的时候,我喜欢写下面这样的代码。目的在于懒得跳到函数的最前面写上extern int g_i
,跳来跳去麻烦,回头要删除代码的时候,也要删两处。而直接在用的地方这么写就方便多了。当然在正式的产品代码中,不推荐这么写。
int foo(){
extern int g_i;
g_i = 10;
}
P33 如何表示类型的最大最小值。
如果想知道各个类型的最大最小值,在C下应该查看三个头文件:limits.h
,float.h
,stdint.h
。其中规定了诸如INT_MAX
,ULONG_MAX
的宏。
在C语言出生的那个年代,处理器各种各样,为了保证C语言的可移植性,C语言的标准甚至连char bit都是用宏来定义的。
define CHAR_BIT 8 /* number of bits in a char */
在C++语言下使用头文件limits
(没有.h的版本)里面的std::numeric_limits
。
std::numeric_limits<int>::lowest()
std::numeric_limits<int>::max()
不太熟悉和用的不多的C++关键字 P43
- alignas
- alignof
- noexcept
- constexpr
- thread_local
操作符替代名 P43
其实挺喜欢用not
代替!
,因为!
有时候太小了,常常忘记没看到。用not
就可读多了,不过似乎没人这么用,那就算了吧。
#include <iso646.h>
int foo(int *p){
if (!p){}
if (not p){}
}
内置类 P45
没听过这个词,
constexpr
这个东西后面的章节还要提,暂时这里就不去深究了。constexpr
指的是一种在编译阶段就能计算出值的表达式,在某些特定场合,比如说声明数组的时候,都必须使用编译期常量,因为编译器必须知道数组的长度。比如说下面的代码是可行的。
const int a = 10;
int b[a + 20] = { 0 };
但是在C++03
的时代,一个常量是否被当成编译期常量完全是编译器自己凭感觉。比如说下面size()
明显就是个常量,但是编译器并不会把它当成一个常量。
int size(){ return 10; }
int c[size()] = { 0 }; //error here
所以直觉上c++11引入这个新的东西的目的就是告诉编译器这里需要一个编译期常量,请试图去计算这个表达式。比如说下面这段代码是正确的,编译器是会在编译阶段来计算sum(10)
的值。PS: VS2013还没有很好的支持constexpr
,所以要尝试下面的代码,可以试试GCC或者Clang
constexpr int sum(int n) {
return n <= 1 ? 1 : n + sum(n - 1);
};
int d[sum(10)] = { 0 };
constexpr指针
如果严谨的说,consexpr
不一定都是编译期就能及计算出来的值,比如说constexpr指针
。从下面这段代码为例,p1
的值必须等到运行阶段才能知道,所以p2 - p1
就不可能是一个constexpr
了
//global
int i, j;
constexpr int * p1 = &i;
constexpr int * p2 = &j;
constexpr int c1 = p2 - p1; // wrong here
某些情况下编译器也能比较聪明的处理下面这种情况。
int a[10];
constexpr int *p = &a[1];
constexpr int *pp = &a[5];
constexpr int c = pp - p; // correct
类型别名 P60
初略的查了一下资料,using和typedef是等价的,所以应该用using来替代掉typedef。主要目的是可读性比较好。
指针,常量和类型别名 P61
书上提到了把pstring
展开后来理解类型是不对的,cstr
和cstr1
其实是不一样的类型。
typedef char* pstring;
const pstring cstr = 0; // cstr is const pointer to char type
const char* cstr2 = 0; // cstr2 is pointer to const char type
这里想了个小技巧,没有特别细究,但估计是正确的方法。当遇到const T
的情况,把前置的const
写到类型后面,如T const
。这样在做展开就没错了。
pstring const cstr = 0; // const pointer to char type;
char * const cstr2 = 0; // const pointer to char type;
当把const
放到后面以后,解读类型就可以方便的从右到左的读出来。
把&
替换成reference to,把*
替换成pointer to;那就是很自然的话了。
char *p;
char const * const & k = p; // K is reference to const pointer to const char;
top-level const和low-level const
如果把const
放到了类型的右边,那么top-level const就是直接靠近变量的标识符的const
,这个const
限制的是变量本身不能改变。为什么会用top-level和low-level这两个词呢,估计是从编译原理里面的语法树分析借鉴来的。
auto关键字。
auto关键字有两个特点,顶层的引用属性和const属性会被去掉。
const int ci = 10;
const int &r = 10;
auto a = &ci;
const auto &b = r;
&c
的类型是int const *
,所以没有top-level const。那么a
的类型就是int const *
&r
的类型是int const &
,去掉顶层的引用变成int const
,那么b
的类型就是(int const) const & b
; 两个const都修饰左边的int
。也就是int const & b
decltype就没有讲透,需要进一步的细化。
这个东西在写模板的时候很有用处,所以需要仔细的了解。
关于表达式i=10的类型
在练习2.37 P64也提到了i=x
的类型是int&
,所以下面这段代码是可行的。但只是在C++下可行,在C下是不可行的。
int i, *p;
(i = 10) = 20;
p = &(i = 1);
当使用字符串初始化std::string时,如果字符串中间包含了'\0'字符,如何绕过去。
如例子s2,可以使用另外一个构造函数Constructs the string with the contents of the range [first, last).
int main(){
char s[] = "first\0second";
string s1(s); // only get "first "
cout << s1.length() << " : " << s1 << endl;
string s2(s, s + sizeof(s));
cout << s2.length() << " : " << s2 << endl;
}
输出是
5 : first
13 : first second