第二章
变量定义
初始化与赋值
-
初始化不是赋值,初始化的含义是创建变量时赋予一个初始值。
-
赋值的含义是把对象的当前值擦除,以一个新值替代。
声明与定义的区别
- 声明:规定了变量的类型和名字。
- 定义:除声明之外,还需要申请存储空间。
如果想声明一个变量,而非定义它,需要使用extern关键词。
extern int i; // 声明i而非定义i
int j; // 声明并定义j
变量只能被定义一次,但可以被多次声明。
变量命名规范
- 用户自定义类名一般以大写字母开头。
- 多个单词用 ‘_’ 隔开。
引用
引用:为一个已经存在的对象起另外一个名字。
-
引用必须被初始化。
-
引用类型的初始值,必须是一个对象(表达式或字面值不可以被引用)。引用本身不是对象,所以不能定义引用的引用。
-
引用要和绑定的对象严格匹配, 但以下两种情况例外:
-
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能够转换成引用的类型即可。
-
常量引用绑定一个非常量的对象、字面值甚至是一个表达式。
int i = 42; const int &r1 = i; //允许将const int&绑定到一个普通int对象上:不能通过r1修改i的值,但可以修改i的值或者其他方式(例如修改i的一个非常量引用)来修改r1的值。 const int &r2 = 42; //正确,r2是一个常量引用。 cosnt int &r3 = r1 * 2; //正确,r3是一个常量引用。 int &r4 = r1 * 2; //错误,r4非常量引用。
-
指针
-
本身就是一个对象,允许对指针赋值和拷贝,指针无须在定义的时候赋值。
-
如果指针指向了一个对象,则允许使用解引用符(*)来访问该对象,使用(&)获取地址。
-
指针的类型都要和它所指向的对象严格匹配, 但以下情况例外:
-
允许一个指向常量的指针指向非常量对象。
const double pi = 3.14; const double *cptr = π //正确,cptr是一个指向常量的指针 double val = 3.14; cptr = &val; //正确,但是不能通过*cptr来改变val的值
**注:**所谓指向常量的指针或引用,不过是指针和引用的自以为是,告诫自己不能去修改所指向的值。而一个常量之所以只能被指向常量的引用或指针指向,是因为如果引用或指针没有告诉自己是指向的常量,那么就可以通过自己来修改指向的值,而指向的值又是常量不可修改,矛盾。例:
const double pi = 3.14; double *cptr = π //假设可行 *cptr = 1111; //对其解引用赋值,则此时cptr指向的pi的值被改变,显然pi是一个常量,不可被修改,错误
-
-
使用未经初始化的指针是引发运行时错误的一大原因。
在大多数编译器环境下,如果使用了未经初始化的指针,则该指针所占内存空间的当前内容将被看作一个地址值。访问该指针,相当于去访问一个本不存在的位置上的本不存在的对象。糟糕的是,如果指针所占内存空间中恰好有内容,而这些内容又被当作了某个地址,就很难分清它到底是合法的还是非法的了。
赋值与指针
-
一旦定义了引用,就无法令其再绑定到另外的对象。
-
区分一条赋值语句,到底是改变了指针的值,还是改变了指针所指对象的值的办法是记住赋值永远改变的是等号左侧的对象。
理解复合类型的声明
-
指向指针的指针
** 表示指向指针的指针
*** 表示指向指针的指针的指针
-
指向指针的引用
引用不是对象,不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。
const限定符
-
定义:const用于定义一个变量,它的值不能被改变。const对象必须初始化。
-
默认状态下,const对象仅在文件内有效。当多个文件出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。
-
如果想让const变量在文件间共享,则使用extern修饰。
顶层const
对指针而言,顶层const表示指针是个常量,底层const表示指针所指的对象是一个常量。更一般的是,顶层const可以表示任意的对象是常量,底层const则与指针和引用等复合类型的基本类型部分有关。
int i = 0;
int *const p1 = &i; //顶层const
const int ci = 42; //顶层const
const int *p2 = &ci; //允许改变p2的值,这是一个底层
const int *const p3 = p2; //靠右的const是顶层const,靠左的是底层const
const int &r = ci; //用于声明引用的const都是底层const
constexpr常量表达式
-
常量表达式是指值不会改变且在编译过程中就能得到计算结果的表达式,显然字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
-
常量表达式的值需要在编译时就计算,因此声明constexpr时用到的类型必须有所限制,因为这些类型比较简单,称为字面值类型。
指针和应用都能定义为constexpr, 但它们的初始值受到限制,constexpr指针初始值必须是nullptr或者0。 -
如果在constexpr声明中定义了一个指针,constexpr仅仅对于指针有效,与指针所指的对象无关,相当于顶层const。
处理类型
类型别名
关键字:typedef,C++后来引入了一种新的方法,别名声明来定义类型的别名。
typedef double wages; //wages现在是double同义词
typedef wages base, *p //现在base是double的同义词没有问题,注意!p是double * 的同义词
using SI = Sales_item; //SI是Sales_item的同义词
注:
如果某个类型别名指代的是复合类型或者常量,将他们使用到声明语句中会产生很多意想不到的后果。
typedef char* pstring;
const pstring cstr = 0; //cstr是指向char的常量指针
const pstring *ps; //ps是一个指针,它的对象是指向char的常量指针
-
正确:pstring是一个指向char的指针,因此,const pstring就是指向char的常量指针,这个指针是个常量,不可修改。而非指向常量字符的可修改的指针。
-
错误:将pstring按原式替换成char*得到 const char * cstr = 0;这样const就变成修饰char,const char成了基础类型数据类型,此时cstr就是一个指向const char的指针。
auto类型说明符
-
auto允许一行有多个变量声明,但是必须为同一类型,通过初始值来确定类型,因此必须初始化。
-
auto一般会忽略掉顶层const,同时底层const则会保留下来,比如当初始值是一个指针常量时:
-
int i = 0, &r = i; const int ci = i, &cr = ci; auto b = ci; //b是一个整数(ci是顶层const特性被忽略了) auto c = cr; //c是一个整数(cr是ci的别名,ci本身是一个顶层const) auto d = &i; //d是一个整型指针(整数的地址就是整数的指针) auto e = &ci; //e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
-
-
如果希望推断出auto的类型是一个顶层cosnt,需要明确指出或将引用的类型设为auto
-
const auto f = ci; //ci的推断类型是int,f是const int auto &g = ci; //g是一个整型常量引用,绑定到ci auto &h = 42; //错误:不能为非常量引用绑定字面值 const auto &j = 42; //正确:可以为常量引用绑定字面值
decltype类型指示符
- decltype 返回表达式变量的类型(包括顶层const 和引用在内),可以不初始化。
const int ci = 0, &cj = ci; decltype(ci) x = 0; //x的类型是const int decltype(cj) y = x; //y的类型是const int&,y绑定到变量x decltype(cj) z; //错误:z是一个引用,必须初始化
-
在c++中引用从来都是作为其所指对象的同义词出现的,只有用在decltype处是一个例外。decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。
-
int i = 42,*p = &i, &r = i; decltype(r + 0) b; //正确:int&+int得到int,因为b是一个未初始化的int decltype(*p) c; //错误:c是int&,必须初始化,如果表达式的内容是解引用操作,则decltype将得到引用类型
*解引用指针可以得到指针所指的对象,而且还能给这个对象赋值,因此,decltype(p)的结果类型就是int&,而非int。
-
-
decltype的表达式只要加上括号的变量,结果将是引用
-
decltype ( (i) ) d; //错误:d是int&,必须初始化 decltype (i) e; //正确:e是一个(未初始化的int)
个人见解,传递的是一个表达式,而不是一个单纯的结果,所以是引用。
-
-