layout: post
title: C++prime读书笔记(一)C++基础
description: C++prime读书笔记(一)C++基础
tag: 读书笔记
iostream库包含两个基础类型istream和ostream,输入流和输出流,一个流就是一个字符序列。
输入cin
输出cout
错误cerr
(输出警告和错误信息,例如在内存溢出时,借内存写错误信息) 日志clog
除了cin,剩余三个用法是一致的。
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2;
if (v1 == 0)
{
std::cerr << "input can not be zero" << std::endl;
}
std::clog << "compute result" << std::endl;
std::cout << v1 + v2 << std::endl;
return 0;
表示该变量或者函数在别的文件中已经定义,提示编译器在编译时要从别的文件中寻找。
使用extern 时不要初始化变量,否则extern则会失去作用。extern int i ; // 声明i
int i; //声明并定义 i
C++支持分离式的编译,如果在多个文件中使用同一个变量,必须将声明和定义分离,定义必须出现且只能出现在一个文件中,而其他用到该变量的文件必须对其声明,但不可以重复定义。
int ival = 1024;
int &refVal = ival; // refval是ival的别名
int &refVal; // 报错:引用必须初始化。
int i1 = 1024, i2 = 2048;
int &r1 = i1, &r2 = i2;
int i3 = 56, &r3 = i3; //也可以在同一行定义并引用
int &r4 = 10; // 报错:引用初始值必须是对象
double i = 5.0;
int &r5 = i; // 报错:引用类型与初始值i的类型不一致
int i5 = 45;
const int &r5 = i5; //允许将const int& 绑定到普通的int对象上
const int &r6 = 42; // 常量引用初始化可以是常量, 5中提到的特例
const int &r7 = 2*r5;
int &r8 = r5*2; // 普通类型的int&不可绑定到常量引用。
C++11,引入了右值引用,即必须绑定到右值的引用,通过&&
而非&
获得右值引用。右值引用的重要性质是——它只能绑定到一个将要销毁的对象上,由此实现将一个右值引用的资源“移动”到另一个对象中。
左值引用我们一般不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。而右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上。
int i = 42;
int &r = i;
int &&rr = i; // 报错:不能将一个右值引用绑定到一个左值上。
int &r2 = i * 42; // 报错:i*42是一个右值表达式
const int &r3 = i * 42; // 正确,const引用是可以绑定到右值表达式的
int &&rr2 = i * 42; // 正确:将rr2绑定到乘法计算结果上
右值引用只能绑定临时对象
这两个特性意味着:使用右值引用的代码可以自由接管所引用对象的资源。
这两个特例实际上与引用中类型匹配是一样的
最好的做法是固定一种写法,例如prime中固定将类型修饰符*,&与变量名连在一起
int i = 1024, *p = &i, &r = i;
int* p1, p2; // p1是指向int的指针,p2则是int类别的变量而已
int *p1, p2; // 与上边写法等价
extern
关键字。const int temp = dval;
再将temp赋值给引用const int &r6 = temp;
int i = 42;
const int &r1 = i; // 常量引用可以绑定普通int对象
const int &r2 = 42;// 可以用字面值或者表达式初始化常量引用。
const int &r3 = r1 * 2;// 可以用字面值或者表达式初始化常量引用。
int &r4 = r1 * 2; // 报错:非常量引用则不可用字面值或者表达式初始化常量引用。
int &r5 = ci // 报错:非常量引用不可以引用常量对象
double dval = 3.14;
const int &r6 = dval; // 可以用其他数据类型的结构初始化常量引用,程序会先自动执行类型转换。
const double pi = 3.14;
const double *cptr = π // pi本身是常量,不可以修改pi
double dval = 3.14;
cptr = &dval; // dval 本身并没有被定义为常量,而仍然可以用来初始化cptr,不可以通过cptr修改dval的值,但可以通过其他方式修改
指针是一个对象,const声明的指针必须初始化,一旦初始化后,则它的值(存放在指针中的地址)就不能再改变了(指向不可变)。将*放在const前,代表指针常量,即指针中的常量,指向不可更改。
const指针所指对象能否修改,不取决于指针是否为常量指针,而取决于所指对象本身是否为常量。
int errNumb = 0;
int *const curErr = &errNumb; // curErr 将一直指向errNumb
const double pi = 3.14;
const double *const pip = π // pip是一个指向常量double对象的指针常量
指针本身是一个对象,又可以指向另一个对象,指针本身是否为常量和所指对象是否为常量是两个独立的问题,用顶层const
表示指针本身是个常量,而用底层const
表示指针所指对象为一个常量。顶层const表示指针存放的地址不可更改,即,指向不可更改,而底层const表示指针指向的对象不可更改。
顶层const: *const,指针常量,指针中的常量,指向不可修改。
底层const:const (int)✳, 常量指针,常量的指针,所指对象视为常量,不可改变所指对象。
int i =0;
int *const p1 = &i; // 顶层const,不可改变p1
const int ci = 42;
const int *p2 = &ci; // 允许改变p2,不可改变ci,这是底层const
const int *const p3 = p2; // 靠右的const是顶层const,靠左的const是底层const
const int &r = ci; // 用于声明引用的const都是底层const
当执行对象的拷贝操作时,顶层const和底层const区别明显。
其中顶层const不受影响,底层const的限制不可忽视。
一般来讲,非常量可以转为常量,而常量不可转为非常量。
i = ci; // 正确
p2 = p3; // p2和p3所指对象类型相同,p3顶层const的部分不受影响。
int *p = p3; // 错误,p3包含底层const的含义,即所指对象不可修改,而p没有,如果这样定义,可能出现通过p修改常量的错误操作
p2 = p3; // 正确,p2和p3都是底层const
p2 = &i; // 正确,int * 可以转成 const int *
int &r = ci; // 错误,普通的int不可以绑定到int常量上,如果这样做,可能出现通过r修改常量ci的错误操作
const int &r2 = i; // 正确,const int & 可以绑定到一个普通的int上。
总结:
事实上底层const出现限制的原因主要在于由于底层const所指对象为常量,故绑定引用或者给指针赋值时,可能出现通过该引用或指针去修改常量的后果,出现这种后果的拷贝操作都是错误的
常量表达式:值不会改变且在编译过程中即可算出结果的表达式,字面值是最基本、最简单的常量表达式。
constexpr :
C++11允许将变量声明为constexpr类型以便于由编译器来验证变量值是否是常量表达式。在新标准下可以定义一种特殊的constexpr函数去初始化一个constexpr变量。
注意:如果在consexpr声明中定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关。
typedef double wages;// wages是double的同义词
typedef wages base, *p; // base 是double的同义词,p是double*的同义词
using SI = Sales_item; // SI 是 Sales_item的同义词
decltype(f()) sum = x; // sum的类型就是函数f的返回值类型
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x的类型为const int
decltype(cj) y = x; // y的类型是const int&
decltype(cj) z; // 错误,z是引用,必须初始化
int i = 42, *p = &i, &r = i;
decltype (r + 0) b; // 表达式加法的结果类型为int,故b是未初始化的int类型
decltype(*p) c; //错误,p本身是int类型,而*p,则是int&的引用类似,引用必须初始化。
decltype((i)) d; //错误,d是int&,必须初始化
decltype(i) e; // e 是未初始化的int
string s;
cin >> s;
cout << s << endl;
string line;
// 每读入一整行
while(getline(cin, line))
cout << line << endl; // 循环读入一整行,并打印
vector<string> v1{"a", "an", "the"}; // 列表初始化
vector<string> v2("a", "an", "the"); // 错误,只能用花括号初始化
vector<int> v1(10); // v1是10个0
vector<int> v2{10}; // v2是1个10
vector<int> v3(10, 1); // v3是10个1
vector<int> v4{10, 1}; // v4是1个10,1个1
unsigned cnt = 42;
constexpr unsigned sz = 42;
int arr[10]; // 10个整数的数组
int *parr[sz]; // 42个整型指针的数组,元素个数可以用常量表达式声明
string bad[cnt]; // 错误,cnt不是常量表达式
string strs[get_size()]; // 当get_size()返回值类型是constexpr时正确,否则错误。
int a[] = {0,1,2};
int a2[] = a; // 错误,不能用一个数组初始化另一个数组
a2 = a; // 错误,不允许用一个数组直接赋值给另一个数组
int *ptrs[10];
int &refs[10]; // 错误,引用非对象,不存在引用的数组
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; // arry是数组的引用,该数组含有10个指针。
int arr[] = {1,2,3,4,5,6};
int *pbeg = begin(arr), *pend = end(arr);
while(pbeg != pend){
pbeg++;
}
int *p = &ia[2]; // p指向数组ia索引为2的元素
int j = p[1]; // j等价与 ia[2 + 1]
int k = p[-2]; // k等价与ia[2 - 2]
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt];
for(size_t i = 0; i != rowCnt; i++){
for(size_t j = 0; j != colCnt; j++){
ia[i][j] = i*colCnt + j;
}
}
size_t cnt = 0;
for(auto &row : ia){
for(auto &col : row){
col = cnt;
++cnt;
}
}
int ia[3][4];
int (*p)[4] = ia; // p是一个指针,指向大小为4的整型数组(ia中的数组1)
p = &ia[2]; // p指向ia的尾元素
int i = 1024;
int k = -i; // k是-1024
bool b = true;
bool b2 = -b; // 布尔值不参与运算,故b2还是true
int ival, jval;
ival = jval = 0; // 正确:都被赋值为0
int kval, *pval;
kval = pval = 0; //错误,不能把指针的值赋值给int
string s1, s2;
s1 = s2 = "OK"; // 字面值可以转为string对象
注:后置递增运算符的优先级高于解引用运算符
auto pbeg = v.begin();
// 输出元素直至遇到第一个负数为止
while(pbeg != v.end() && *beg >= 0){
cout << *pbeg++ << endl; // 输出当前的值,并将pbeg向前移动一个元素
}
finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";
cout << "hi" << "there" <<endl;
((cout << "hi") << "there") << endl; // << 满足左结合律,上边的写法等价这一行
cout << 42 + 10; // 正确,输出52
cout << (10 < 42); // 正确,输入1
cout << 10 < 42; // 错误,<< 运算符优先级比 < 高,这句话试图比较cout 和42
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
for(auto &r : v){
r*=2;
}
// 范围for语句等价于传统的for语句的形式为:
for(auto beg = v.begin(), end = v.end(); beg != end; ++beg){
auto &r = *beg; // r必须是引用类型才能对元素执行写操作
r *= 2;
}
Sales_item item1, itme2;
cin >> item1 >> item2;
if(itme1.isbn() == itme2.isbn()){
cout << item1 + item2 << endl;
return 0; //表示成功
}else{
return -1; // 表示失败
}
// 在真实的程序中,应该把对象相加的代码和用户交互的代码分离开,下边是抛出异常的写法:
if(item1.isbn() != itme2.isbn())
throw runtime_error("Data must refer to same ISBN!");
类型runtime_error是标准库异常类型的一种。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。
下边书写一个处理异常的代码:
while(cin >> item1 >> item2){
try{
// 执行添加两个Sales_item对象的代码
// 如果添加失败,代码抛出一个runtime_error异常
}catch(runtime_error err){
// 提醒用户两个ISBN必须一致,询问是否重新输入
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n'){
break; // 跳出while循环
}
}
}
size_t count_calls(){
static size_t ctr = 0; // 调用结束后,这个值仍然有效
return ++ctr;
}
int main(){
for(size_t i = 0; i != 10; ++i){
cout << count_calls() << endl;
}
}
void fcn(const int i){};
void fcn(int i){}; //错误:忽略顶层const, 重定义fcn
// 尽管形式不同,但这三个print函数等价
// 每个函数都有一个const int*类型的形参
void print(const int*);
void print(const int[]);// 函数的意图是传入数组,但会被转为首元素指针,故实际上与上一行是等价的
void print(const int[10]);// 维度是期望的数组含有的元素个数,实际上传入的却不一定。
void print(int (&arr) [10]){
for(auto elem : arr){
cout<< elem << endl;
}
}
在数组的章节(3.5.1)中,对于复杂数组的声明应该从数组名字开始,由内向外(数组名向两边)来理解。 &arr,说明arr是一个引用,再看两边知道,arr绑定的是一个大小为10的整型数组。注意&arr两端的括号必不可少,否则有歧义。另外,由于维度属于数组声明的一部分,因此print()函数被限制只能接受大小为10的整型数组作为实参。
f(int &arr[10]) // 错误:将arr声明为了引用的数组,&与int共同构成了数组元素的类型
f(int (&arr)[10]) // 正确,arr是具有10个整型元素的数组的引用
main
函数位于prog
之内,我们向程序传递下边的选项:prog -d -o ofile data0
int main(int argc, char *argv[]){……}
argv的第一个元素
指向程序的名字
或者一个空字符串
,接下来的元素依次传递命令行提供的实参,最后一个指针之后的元素值保证为0.argv[0] = "prog";
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
initializer_list
的标准库类型;initializer_list<T> lst; // 默认初始化T类型元素的空列表
initializer_list<T>lst{a,b,c……}; // 使用花括号初始化成员列表
lst2(lst);
lst2 = lst; // 拷贝或者赋值一个initializer_list对象不会拷贝列表中元素,原始列表和副本是共享元素的。
lst.size();
lst.begin();
lst.end();
void error_msg(ErrCode e, initializer_list<string> il)
{
cout << e.msg() << ": ";
for (const auto &elem : il) // 既然initializer_list中是不可改变的常量元素,那么就应该使用常量引用来接收元素
cout << elem << " " ;
cout << endl;
}
// 调用error_msg
if(expected != actual)
error_msg(ErrCode(42), {"functionx",expected , actual});
else
error_msg(ErrCode(0), {"functionX", "okay"});
char &get_val(string &str, string::size_type ix){
return str[ix];
}
int main(){
string s("a value");
cout << s << endl; // a value
get_val(s, 0) = 'A'; // 将s[0]的值改为A
cout << s << endl; // A value
}
vector<int> smallTow(vector<int> arr){
sort(arr.begin(), arr.end());
return {arr[0], arr[1]};
}
int (*func(int i))[10];
上边的表达可以使用C++11标准的尾置返回类型。尾置返回类型跟在形参列表后边,并以一个->符号开头,本该出现返回值类型的地方使用auto代替。
auto func(int i) -> int (*)[10];
func 返回一个指针,该指针指向含有10个整数的数组。
使用->指向的位置返回类型,表达更加清晰明了。
将函数指定为内联函数可以避免函数调用时的开销
,通常内联函数会在它每个调用点上“内联地”展开,例如我们把shortString函数定义为内联函数,它实际的调用方式为:cout << shortString(s1, s2) << endl;
cout << (s1.size() < s2.size () ? s1 : s2) << endl;
inline
,就可以将它声明为内联函数:inline const string & shortString(const string &s1, const string &s2){
return s1.size() <= s2.size() ? s1 : s2;
}
一般来说,内联机制用于优化规模较小,流程直接,频繁调用的函数。
2. constexpr函数:能用于常量表达式的函数。定义constexpr函数与普通函数类似,不过要遵循如下规定:
bool lengthCompare(const string &, const string &);
该函数的类型是bool(const string &, const string &)
。想声明一个可以指向该函数的指针,只需要用指针替换函数名即可:注pf是指针名,两边的括号必不可少
bool (*pf) (const string &, const string &); // 未初始化
pf = lengthCompare; // pf指向名为lengthCompare的函数
pf = &lengthCompare; // 等价的赋值语句,即取址符号是可选的
bool b1 = pf("hello","goodbye"); // 无须解引用pf直接调用
bool b2 = (*pf)("hello", "goodbye"); //一个等价的调用
bool b3 = lengthCompare("hello", "goodbye"); // 另一个等价的调用
// 第三个形参是&
void useBigger(const string &s1, const &s2, bool pf(const string &, const string &));
// 等价的声明
void useBigger(const string &s1, const string & s2, bool (*pf)(const string &, const string &));
// 等价的声明
void useBigger(const string &s1, const string & s2, lengthCompare);
// Func 和 Func2是函数类型
typedef bool Func(const string&, const string&); // 类型别名声明
typedef bool decltype(lengthCompare) Func2; // 等价的类型
// Funcp 和Funcp2是指向函数的指针
typedef bool(*Func)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;
// 使用类型别名重新声明useBigger,虽然这里传入的是函数,但编译器会自动把他转为函数指针
void useBigger(const string &s1, const &s2, Func);
void useBigger(const string &s1, const &s2, Func2);
int (*f1(int))(int *, int);
auto f1(int) -> int (*)(int*, int);
using F = int(int*, int); // F是函数类型
using PF = int(*)(int*, int); // PF是指针类型
PF = f1(int); // PF是指向函数的指针,f1返回函数的指针
F f1(int); // 错误,F是函数类型,不可返回函数,只能返回函数指针
F *f1(int); // 正确,显示地指向返回类型是指向函数的指针。
int (*f1(int))(int *, int);
auto f1(int) -> int (*)(int *, int);
decltype(sumLength) *getFcn(const string &);
Sales_data(const std :: string &s) : bookNo(s){}
Sale_data(const std :: string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p*n){}
TreeNode(int x):val(x), left(nullptr), right(nullptr){}
TreeNode(int x, TreeNode* _left, TreeNode* _right) : val(x), left(_left), right(_right){}
mutable
修饰可变数据成员,这样的成员变量,即是在常函数中也可以修改它的值。class Window_mgr{
private:
// 这个window_mgr追踪的screen
// 默认情况下,一个window_mgr包含一个标准尺寸的Screen
std::vector<Screen> screens{Screen(24, 80, ' ')};
size = 1;
}
class Screen{
public:
Screen &set(char);
Screen &set(pos, pos, char);
};
inline Screen &Screen::set(char c){
contents[cursor] = c;
return *this;
}
inline Screen &Screen::set(pos r, pos col, char ch){
contents[r*width + col] = ch;
return *this;
}
// 把光标移动到指定位置,设置该位置的字符值
myScreen.move(4, 0).set('#');
// 上边的一条语句等价于下边两条语句。
myScreen.move(4.0);
myScreen.set('#');
基于const的重载:
通过区分成员函数是否是const的,我们可以对其进行重载。class Screen{
public:
// 根据对象是否是const重载display函数
Screen &display(std::ostream &os){do_display(os); return *this;}
const Screen &display(std::ostream &os) const{os << contents;}
private:
// 该函数负责显示screen的内容
void do_display(std::ostream &os) const {os << contents;}
// 其他成员与之前的版本一致
}
当一个成员调用另一个成员时,this指针在其中隐式的传递。当display调用do_display时,它的this指针隐式传递给do_display。而当display的非常量版调用do_display时,它的this指针将隐式地从指向非常量的指针转换成指向常量的指针。
当do_display完成时,display函数各自返回解引用的this所得的对象,在非常量版本中this指向一个非常量对象,返回普通引用,而const成员返回一个常量引用。
Screen myScreen(5,3);
const Screen blank(5,3);
myScreen.set('#').display(cout);
blank.display(cout);
class ConstRef{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
// 默认初始化
ConstRef::ConstRef(int ii){
i = ii; // 正确
ci = ii; // 错误,不可给const赋值
ri = i; //错误,ri没被初始化
}
// 正确的构造函数的格式是:
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) {}
最好令构造函数初始值的顺序与成员声明时的顺序一致,并且尽量避免使用某些成员初始化其他成员。
class Sales_data{
public:
// 非委托构造函数使用对应的实参初始化成员
Sales_data(std::string s, unsigned cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt*price){}
// 其余构造函数全部委托另一个构造函数
Sales_data():Sales_data("", 0, 0){}
Sales_data(std::string s):Sales_data(s, 0, 0){}
Sales_data(std::istream &is):Sales_data(){read(is, *this);}
};
explicit
构造函数:explicit
关键字通常用来将构造函数标记为显式类型转换,即在创建对象的时候不能进行隐式类型转换。
class Sales_data{
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) {}
explicit Sales_data(const std::string &s):bookNo(s)
explicit Sales_data(std::istream&);
};
// 错误,explicit关键字只允许出现在类内构造函数声明处
explicit Sales_data::Sales_data(istream& is){
read(is, *this);
}
Sales_data item1(null_book); //正确,直接初始化
Sales_data itme2 = null_book; // 错误,不能将explicit构造函数用于拷贝形式的初始化
struct Data{
int ival;
string s;
};
Data vall = {0, "Anna"}; // 注意初始化的顺序要与声明的顺序一致
字面值常量类:数据成员都是字面值类型的聚合类是字面值常量类,字面值类型的类可能含有constexpr函数成员,constexpr函数的参数和返回值必须是字面值类型。如果一个类不是聚合类,如果符合以下要求也是字面值常量类:
constexpr构造函数,constexpr构造函数必须初始化所有数据成员,初始值使用constexpr构造函数或者是一条常量表达式。
class Debug{
public:
constexpr Debug(bool b = true):hw(b), io(b), other(b){}
constexpr Debug(bool h, bool i, bool o):hw(h), io(i), other(o){}
constexpr bool any(){return hw || io || other}
private:
bool hw; // hardware error
bool io; // io error
bool other; // other error
}
静态成员函数和静态成员不与对象绑定在一起
,不包含this指针
,所有对象的静态成员共享一份内存数据
。静态成员函数不能声明为const
,const用于后置修饰函数时只用于限定成员函数,意味着将被修饰的成员函数的隐式参数——this指针由原来的Class*const变为constClass※const类型,使得在该成员函数内不能修改成员属性
,除非该属性被mutable修饰。而static类函数并没有隐式的this指针
,因为其本质上还是属于C函数——满足__cdecl调用协定。而成员函数被称为__thiscall,带有隐式的this指针参数。static关键字只出现在类内部的声明语句中
一般而言,我们必须在类的外部定义和初始化每个静态成员,不能在类的内部初始化静态成员。
cin cout clog cerr
/*注释不可嵌套
使用//单行注释
unsigned默认是unsigned int
可寻址的最小内存块称为“字节”8bit
存储的基本单元为“字”4/8 Byte
给无符号类型一个超过它范围的值结果是该值对无符号类型表示数值总数取模所得余数。
例如
unsigned char c =-1; // 假设char占8位,值为255(-1 mod 256)
不允许给非常量引用赋值常量对象
但允许给一个常量引用绑定非常量的对象,字面值或者一般表达式。
常量引用绑定非常量是允许的,不允许通过常量引用修改值,但被绑定的对象本身不受影响,可以赋值或者通过其他引用来修改。
指向常量的指针或者引用,是他们“自以为是”地认为自己指向了常量,便自觉不去修改所指对象的值,实际上所指对象可能并不受修改的约束。
顶层const表示指针本身是个常量,(指针常量),底层const表示指针所指对象是个常量,(常量指针)。想象一个自上指下的指针,顶层常量是指针本身是常量,底层常量是所指对象是常量。
常量表达式:值不会改变且在编译过程中就能得到计算结果的表达式。
constexpr变量
C++11允许将变量声明为constexpr类型以便于由编译器来验证变量值是否是常量表达式。在新标准下可以定义一种特殊的constexpr函数去初始化一个constexpr变量。
typedef double wages; //给double赋别名wages
C++11规定了一种新的别名声明来定义类型的别名:
using SI =Sales_item;
别名类型使用时,通常将别名替换为原始名称来理解,但对于复合类型别名需要注意,eg:
typedef char *pstring;
这里的意思是给char *起别名为pstring
const pstring cstr = 0; //cstr是指向char的常量指针
若直接替换,则为:
const char *cstr = 0; //这样cstr是一个指向const char的指针。
暂时理解是,原本类型是char指针,const修饰指针,直接替换则变为const修饰char。
auto类型说明符
C++11引入auto,允许编译器通过初始值来推算变量的类型,所以 auto定义的变量必须有初始值。
auto一般会忽略顶层const,而底层const则会保留,例如初始类型是常量整数,auto后就是整数,但初始类型是一个指针,则const属性会保留。
decltype类型指示符
作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,但不实际计算表达式的值。
decltype (f()) sum = x;//sum的类型是函数f的返回类型。