1.变量声明和定义的区别: 定义指的是为变量分配内存空间,并可以赋初值;声明指的是指定变量的类型和名称。定义本身也是一种声明。一般使用extern关键字来进行变量的声明。由此可见,区分声明和定义的关键在于是否分配了内存空间。(注意:一个变量只能被定义一次,但是可以被声明多次)例如:
int i = 0; //定义,既分配了空间,又赋初值
int i; //定义,只分配了空间(实际上编译器会给个奇怪的初值)
extern int i; //声明,extern表明是在别的文件中分配的空间,这里只是用别人的
extern int i = 0; //定义(其他文件也可以使用i),虽然有extern,但是语句为i赋了初值,不分配空间是不能赋初值的
2.变量的作用域:变量的作用域用语句块来判断,从声明开始到本语句块结束为止(找到'}')。定义在所有花括号“{}”之外的全局变量具有全局作用域,全局可见(本文件中,其他文件中需要使用extern声明一下),其他变量具有块作用域,如上所述。当发生作用域嵌套时(一个花括号“{}”包含另外的花括号“{}”),外层声明的变量内层可以直接使用;如果内层也定义了同名变量,则在此变量的作用域内屏蔽外层变量,以此变量为准。
int a; //全局作用域
{ |
int b; //块作用域 |
{ | |
int b;//屏蔽外层b | |
b++; | | |
b--; V | |
} | |
b++; V |
} V
3.指针和引用,它们的区别在于两点:(感觉上引用自带了const属性,才有下面的特性,想想const指针是不是也满足下面?当然,引用和const指针是两个不一样的东西)
1).引用在声明时必须被初始化,而指针不需要(推荐初始化为NULL或者nullptr,这样保险~~)
2).引用的指向是不能改变的,而指针可以
4.delete与void *指针:当你delete一个void *类型的指针的时候,编译器会给出警告,那么这里边出了什么问题呢?在正常的delete一个指针的情况下,会做两件事:
- 如果是class或者struct 的指针,那么会调用它们的析构函数
- 释放内存
不过,对于void *类型的指针,编译器无从知晓它是什么类型的,也就是说无法完成步骤1,即不会调用析构函数;对于需要在析构函数中释放内存的类来说,内存泄漏就发生了。但是,注意步骤2是能够正确完成的,无论这个指针原来是否指向数组,内存都能得到正确的释放(想想也正常,malloc和operator new返回的不都是void * 么?长度信息肯定记录在案了~~)。
5.const 限定符:const 对变量的限定,分为顶层const 与底层const 两种。
- 顶层const:限定此变量本身的值不能够被修改(所以要求声明时就初始化)
- 底层const:限定不能通过此变量修改它指向或者引用的变量(专门限制引用和指针的)
const与指针之间的关系可以按照从右到左来解读:如果const在*号左侧,代表了被定义的变量首先是一个指针,然后是指向常量的指针(底层const),所以不能通过该指针修改被指物;如果const在*号右侧,代表了被定义的变量首先是一个常量,然后是一个常量指针(顶层const),所以不能修改该指针的指向。
int i = 0;
//从右到左解读p1:这是一个常量,一个常量指针,一个指向int的常量指针
int *const p1 = &i; //p1的值(指向)不能修改,顶层const
const int ci = i; //同上,顶层const
//从右到左解读p2:这是一个指针,一个指向int的指针,一个指向int常量的指针
const int *p2 = &ci; //不能通过p2修改ci,底层const
//从右到左解读p3:这是一个常量,一个常量指针,一个指向int的常量指针,一个指向int常量的常量指针
const int *const p3 = p2; //底层const,顶层const
const int &r = i; //不能通过r修改i,底层const
注意:当具有底层const 属性的变量作为右值时,要求对应的左值也必须具有底层const 属性,否则就会出错(为了保证变量不可被修改)。
- const 指针/引用具有“自律性”,const 限定的是指针/引用自身(值或者操作),而并不关心初始化的时候另外一侧是什么,所以可以把const对象、非const对象和不同类型但可转化的值初始化给const指针/引用。例如:const引用可以指向const对象、非const对象等等。
- 但是反过来,const 变量的“要求”(各种限制)要比普通变量要高,所以不能将const 对象初始化或者赋值给一个普通指针/引用,以防止被修改。
- 当非指针/引用类型的const 变量被使用时,它们经常会被忽略顶层const的属性,可以被放在表达式中赋值给一个普通变量(其实是被编译器直接用它们的值替代优化了)。
6.在文件中定义的const对象属于这个文件,对于文件外来说不可见,这与全局对象不同。文件中定义的全局对象,默认含有extern关键字,而const对象不具有,所以属于文件。所以,在所有#include定义了const对象的头文件的文件中,都存在const对象的副本(类型、名称和值都一致)。但是在实际当中,编译器使用为const对象初始化的常量表达式替换文件中的const对象,所以这些const对象又不占用空间。我们可以在const对象之前加上extern将它变为全局的以方便使用。
extern const in bufSize = 1024;
7.constexpr与常量表达式:常量表达式是指值不会改变并且在编译过程中就能得到结果的表达式。在c++11中,可以通过将变量声明为constexpr来表示此变量的值是一个常量表达式。注意,constexpr 指针相当于顶层const指针(指向不能改变),而且它只能指向全局变量或者静态变量,以及nullptr、NULL。
static int si = 0;
constexpr int *p = &si;
8.在c++11中,新增加了别名声明来起到和typedef一样的效果;据说是用来解决typedef与模板的某些问题的.......
using alias_name = origin_name;
9.在c++11中,修改了auto类型说明符,让编译器自动推断一个表达式的类型,然后声明给变量(用多了会不会被接手代码的人骂死.....)。
int radius = 5;
double pi = 3.14;
auto perimeter = pi * radius; //perimeter是double
需要注意的是:
- auto定义的变量必须初始化(当然了,不初始化怎么让编译器去推断类型?)
- 把const 变量赋值给auto,顶层const 属性会被忽视,底层const 属性会被保留
- auto会忽略引用类型,只保留值类型
- auto类型的引用会保留初始化值的顶层const属性
const int ci = 0;
auto a = ci; //a只是int,const不见了
const auto b = ci; //显式声明b为const
auto &r = ci; //r是const引用
const int &ri = ci;
auto rc = ri; //rc只是int,const和ref都不见了
auto &rd = ri; //显式要求rd初始化为引用
可以这么理解:使用const 对象的值的时候,编译器替代优化,所以在auto那里只能看到值,所以”丢失“了顶层const;但是,当使用const 对象地址的时候,编译器只能老老实实的把变量拿过来给auto判断,所以顶层const被保留了下来。同理,使用引用的时候,编译器直接使用值替代了引用,所以auto只剩下了值的类型。
10.在c++11中,新增加了decltype类型指示符,可以用它取出表达式返回值的类型,用来声明一个对象。(有点python中type()函数的感觉哈~~)关于decltype与auto注意两点:
- 推断类型时,decltype括号中的表达式不会被计算,而auto声明时表达式会被计算
- 解引用(*p)、赋值表达式和括号表达式返回的是引用类型,使用decltype会得到引用类型,而auto会得到它们值的类型(感觉auto多做了一层转化...)
11.在c++11中,允许为类内部的数据成员提供一个类内初始值,而不需要再在初始化列表或者构造函数内部初始化了;如果没有为它们指定初始值,那么它们会被默认初始化。(先进生产力的代表啊......)注意:类内初始值必须以=或者{}初始化。
struct Test
{
std::string a; //没初始值,默认初始化为空
int b = 0;
double c = 1.1;
vector<int> v{1,2,3,4,5,6};
}