C++ Primer 学习 《变量类型》

浮点数

一般来说,float占据1个word,double占据2个words.
在64位机上测试:
float:4 bytes,double:8 bytes,long double:8 bytes

一般来说,float精度7位,double 精度16位,long double精度则不确定。


Char

char有三种格式:char(也叫做plain char),signed char,unsigned char,都是占1 byte
根据不同的编译器,plain char可能等同于signed char,也可能等同于unsigned char

二进制、八进制、十六进制

C++中不支持直接编写二进制数,至今不明白为什么。
关于八进制和十六进制数,有两种情况,一种是给数字赋值,一种是给字符赋值。
对于八进制数:
	int i = 0111;//0不能省略,i = 73
	//输出八进制表示的字符时,需要转义符\,对于\后面添加0或不添加0一样,都表示八进制数
	//C++编译器对\后面字符取3位(包括开头的0),如果数字大于255则会编译报错(VS2012)
	//如果超过三位,则将前三位当做一个数,后面的再依次判断
	cout<<"\111"<<endl//输出‘I’
		<<"\0111aa"<<endl//输出“	1aa”,011是水平制表符
		<<"\0400"<<endl//error,0400 = 256 > 255
		<<"\111123"<<endl//输出‘I123’
		<<"\111\123"<<endl;//输出‘IS’
十六进制和八进制差不多:
只是对数字赋值需要用0x或0X开头。
对字符赋值只能\x开头(注意大小写),而且\x后编译器会一直往后读,直到第一个非十六进制数或字符串结束才停止,如果该数字不在0~255之间会报错。
	cout<<"\xff"<<endl//可以,最大情况
		<<"\x111"<<endl;//不可以,0x111 = 273 > 255

变量初始化

在新标准C++中,引入了更多变量初始化方法。
	int a = 0;
	int b = {0};
	int c{0};//根据Primer上说,这个list initialization被拓展了,但是在VS2012没有编译通过
	int d(0);

<span style="white-space:pre">	</span>//根据Primer,list initialization只能被初始化为不会产生数据丢失的情况
	double e = 1.0;
	int f{e};//error,double到int,会产生loss of information
	int g(e);//可以,但是会truncated

变量命名规则

我们定义的变量名(identifier)不应该使用连续下划线(consecutive underscores),或下划线后紧接大写字母(an underscore followed immediately by an uppercase letter),在函数外部,不应该使用下划线作为起始变量名。
注意:以上这些情况在VS2012中全都能编译通过。但是我们知道,系统预先申明的变量,大多采用下划线开头,或双下划线开头形式,我想作者是希望我们避免和系统变量冲突。

全局变量与局部变量

block内的局部变量会覆盖block外的全局变量。但是我们依然能显式调用该全局变量。
int a = 1;
int main()
{
	int a = 0;
	cout<<a<<endl;//print 0
	cout<<::a<<endl;//print 1
	::a ++;
	cout<<::a<<endl;//print 2

	system("pause");
}

Reference

Reference,或叫引用,是比较复杂的东西。之前除了函数内的引用传递外几乎没使用过,这次好好学习一下。
引用是对一个object的别名(alternative name)。申明一个引用时,必须立刻赋值(must be initialized)。
        int ival = 1024,ival2 = 0;
	int &refVal = ival;//refVal refer to ival
        refVal = ival2;//ok
	int &refVal2;//error!没有赋值。
引用的本质,是把reference bind to initializer。上例中,我们将refVal和ival捆绑(bind)。没有解除捆绑(rebind)的办法,除非指定其他的对象
reference其实就是个别名,因此当我们在任何情况使用这个reference时(获取值,给其他变量赋值,运算等),其实编译器是去initializer那里取值。
绑定对象
reference只能绑定对象(object),不能绑定一个字符串(literal)或一般表达式。
int &a = 10;
int &b = "aa";//error,reference只能绑定object


Pointer

指针无疑是C/C++最强大的武器,如果没有指针,也就没有必要使用C/C++了。
由于我学习C++已有一年多,指针的基础内容就不再介绍了。指针需要多多练习,掌握并不难。
初始化
每个指针都应该初始化。
对于暂时没有对象的指针,我们可以这样初始化。
	int *p1 = nullptr;//新标准下推荐使用的方法
	int *p2 = 0;//直接赋为0,无所谓指针是int*还是其他
	int *p3 = NULL;//老版本下常用的方法
使用Pointer作为条件判断
	int ival = 1024;
	int *pi = nullptr;
	int *pi2 = &ival;
	if(pi){
		.......//不会发生
	}
	if(pi2){
		.......//会发生
	}
void* Pointer
这一个很特殊的Pointer。它能接纳任何种类的Pointer
	int a = 0;
	double b = 0.0;
	void *p = &a;//ok
	p = &b;//ok
作为代价,void*的操作符是有限的。
它可以:和其他pointer比较,传递给一个函数或作为函数返回值,给另一个void*赋值。
它不能:对其指向的object进行操作
void *p;
cout<<*p<<endl;//error!
我的理解是,void*作用是,当一个函数可能有多种返回值,那么可以返回一个void*。我们虽然不能直接取得void*指向的数据,但我们可以通过转换得到该数据。
int a = 0;
void *p = &a;
cout<<*p<<endl;//error
cout<<(int*)p<<endl;//ok
通过指针形式转换,就能实现该目的。

Reference to Pointers
由于引用不是一个对象,所以不存在指向引用的指针,但是引用可以指向一个指针。

<span style="font-size:12px;">int i = 43;
int *p;
int *&r = p;
r = &i;//相当于 p = &i;
*r = 0;//相当于 *p = 0;(dereferencing r yields)</span>
其中值得注意的是引用指向指针的initialization语句

<span style="font-size:12px;">int *&r = p;</span>

乍一看很复杂,一会*一会&的。其实规律是这样的,initialization语句要从右往左看。在这里,我们创建一个名为r的变量,它是什么呢?往左看,第一个是&,所以它是一个引用。那这个引用的变量类型是什么呢?再往左看,是int *,所以是一个指向整形指针的引用!这么就清楚啦。

Const

const是极其常用的一个类型。
首先,const在initialization时必须被赋值,因为之后我们不能再更改它的值!
多个文件共用一个const值
const这个常量,常常在多个文件中需要被共同使用,但不同文件下的const object的作用域只是该const所在文件。共用的办法就是使用extern关键字。 注意:我们应该在定义和声明处都添加extern(use keyword extern on both its definition and declaration)
//file1.cc, define and initialize const bufSize
extern const int bufSize = fcn();
//file1.h,same as that in file1.cc
extern const int bufSize;
上面这两个extern都不能省略(VS2012中省略会报错),但如果两个变量是nonconst int,则不会。

Reference to Const
和上一部分一样,指针也可以指向const类型变量
const int a = 1024;
const int &r = a;//记得从右往左看,这是一个指向const int的引用!
r = 42;//error!const不能被赋值
int &r2 = a;//error!!非const引用指向一个const int!
记得之前我们说reference不能bind to常数等,现在,有了const,我们便可以这么做了!甚至,可以将引用指向一个和其本身变量类型不一样的值!
        int a = 0;
        const int &r = a;//ok
        const int &r1 = 42;//ok
	int &r2 = 42;//error!
	const int &r3 = r1 * 2;//ok
	double dval = 0.0;
	const int &r4 = dval;//ok
	int &r5 = dval;//error!但是强烈不推荐这么用!
A Reference to const May Refer to an Object That is Not const!
这是什么意思呢?根据上面一段,我们知道一个const reference(reference to const的简化说法)可以指向非const!所以,我们不能保证,const reference的值是不变的!
	int a = 1024;
	const int &r = a;//r = 1024
	a = 1;//现在r = 1

	double d = 0.0;//如果const reference和underlying object的变量类型不同,那么const reference的值不会被改变
	const int &r2 = d;//r2 = 0
	d = 1.0;//依旧有r2 = 0,因此,这种编程方法真的不推荐!

const 和 pointer--top level const和low level const

pointer自然也有const类型,但为什么要把这一部分特别拿出来单独讲呢?因为const和pointer有两种组合(top level const和low level const),对于初学者常常会搞混,对日后的编程非常不利,因此这里希望把这两个概念弄清楚!
pointer to const
和reference to const非常相似,一个pointer to const意味着这个pointer指向的内容不能被通过这个pointer改变。只是,pointer赋值两边必须是同一个变量类型(这才对嘛!)
	int a = 0;
	const int *p = &a;//ok,int能转成const int
	const int b = 0;
	const int *p2 = &b;//ok
	double c = 0.0;
	const int *p3 = &c;//error!两边变量类型不一样!

	p = &b;//ok!pointer to const可以改变pointer本身!(即可以改变pointer指向哪个object)
	*p = 1;//error!pointer to const不能改变pointer指向的object

	p = &a;//这时*p = 0
	a = 2;//现在*p = 2
OK,到目前为止你觉得const和pointer还很容易,下面就是麻烦的开始了。
const Pointer
由于pointer本身也是object,所以pointer本身也可以被定义成const!这种const pointer决定了它指向哪个object,即这种指向关系不能被改变!
	int a = 0,b = 1;
	int *const p = &a;//p是一个const pointer,不是pointer to const
	p = &b;//error!p的指向关系不能被改变
	*p = 1;//ok!p指向的值可以改变

	const int c = 0;
	const int *const p2 = &c;//p2既是const pointer又是pointer to const
	p2 = &a;//error
	*p2 = 1;//error
如何弄清楚?牢记从右往左看原则!
top-level const 和low-level const
我们将object自身是const的情况称为top-level const,而将变量指向的object是const的情况t称为low-level const。请牢记这两个术语和对应关系,以后将会多次提到!
另外,如果是内置变量,如int,double,class,它们的const应该是top-level。
	const int a = 0;//a是top-level const
	const int *p = &a;//p是low-level const
	int *const p2 = &a;//p2是top-level const
	const int &r = a;//const reference永远是low-level const
当我们在复制(copy)一个变量到另一个变量时,top-level const会被忽略。而low-level const永远不会被忽略。简单来说,赋值时,等号两边的变量必须有相同的low-level const,或者存在一个转换函数。值得注意的是,这是C++ Primer里的说法,我觉得有点问题,具体大家可以看下面的例子。我们是不能把const int赋给一个int* 指针的,也不能赋给一个int &引用,其实这很好理解,因为这两个指针和引用都可以修改原来的const int值,那就会出问题了。
	const int a = 0;
	int b = a;//ok,top-level const ignored
	int *const p1 = &a;//error
	const int *p2 = &a;//ok
	int *p = p1;//ok
	p = p2;//error

	int &r = a;//error
	const int &r = a;//ok

Constexpr

在编译时,编译器就能获取的值,称为const expression。一般来说,直接初始化的const,由const和literal计算得到的const是const expression,其他则不是。
const int a = 20;//是constexpr
const int b = a + 1;//是
int c = 20;//不是
const int d = getsize();//不是
在新标准下,增加了constexpr这个变量关键字,它表明,后面的值必须是一个const expression,一个const expression默认是一个const
constexpr int a = 20;//OK
constexpr int b = a+1;//OK
constexpr int c = getsize();//如果getsize()是一个constexpr函数,则ok,否则error
对于一个函数内定义的变量,往往不存在一个固定地址,因此无法用constexpr指针指向这样的变量,而如果是函数外的变量,则往往可以。
另外,constexpr定义的指针,这个constexpr规定了指针本身是const,而非指针指向的对象是const。
const int *p = nullptr;//p是pointer to const
constexpr int *p2 = nullptr;//p2是const pointer
这就说明constexpr定义的object是top-level的!
关于constexpr,是一个新出来的东西,无奈我的VS2012还不支持这个变量,无法测试,我会尽快补上测试结果!

Type Aliases

我们常常希望对一个类型进行重命名。为什么呢?比如你做一个价格统计,假设将所有变量存为double类型。
现在,我们在声明时只能用:double objectname
设想,我们给double附一个别名,叫做price,那么我们声明时候可以这么写:price objectname。在很多情况下这样会让我们编程更加直观。
typedef
typedef double wages;//wages现在可以当做double使用
typedef wages base, *p;//typedef可以嵌套。base相当于double,p相当于double*
using
C++ 11中引入了新的type alias定义方法
using price = double;
using price2 = double*;//和上面作用一样
typedef和using肯定是有区别,不然C++编委会不会这么蛋疼。。但我目前还不清楚区别在哪,日后补充!
pitfall
使用typedef/using 有一个非常值得注意的事,就是和pointer,const配合使用。
<span style="white-space:pre">	</span>typedef double *doubleAlias;
<span style="white-space:pre">	</span>typedef const double *doubleAlias2;

	double a = 0,b = 0;
	const doubleAlias d1 = &a;//d1是一个const pointer,high-level const!
	const double *d2 = &a;d2是一个pointer to const,low-level const!
	*d1 = 1;//ok
	*d2 = 1;//error
	d1 = &b;//error
	d2 = &b;//ok

	doubleAlias2 d3;//d3是pointer to const
	d3 = &a;//ok
	*d3 = 0;//error
	const doubleAlias2 d4 = &a;//d4是const pointer to const
	d4 = &b;//error
	*d4 = 0;//error
也就是说,当我们typedef后,使用alias时不能简单的把原来的内容带入进去!如果前面有const,我们要把const和typedef的alias一起看。
比如:const doubleAlias表示这个doubleAlias本身是const,而doubleAlias是指向double的指针,所以const doubleAlias的意思是一个const指针,指向double。再比如:const doubleAlias2表示这个doubleAlias2本身是const,而doubleAlias2是指向const double的指针,所以const doubleAlias2的意思是一个const指针,指向const double!

Auto

C++ 11引入了一个很神奇的变量声明——AUTO type。它让编译器自己选择适合的变量类型,以此对变量做申明。
使用auto作为类型声明的变量必须赋初值(must have an initializer)。
auto a1;//error
auto a2 = val1+val2;//ok
使用auto作为类型声明,同时声明多个变量时,各个变量必须是统一的类型。
auto i=0; *p = &i;//ok,i is int, p is pointer to int
auto a1 = 0, a2 = 1.0;//error
使用reference赋给auto,那么auto的类型就是reference的类型
int i=0, &r = i;
auto a = r;//a 是 int
auto忽略top-level const,保留low-level const,如果希望保留top-level const需要显式声明
int i=0;
const int ci = 0, &cr = ci;
auto a1 = ci;//a1 是 int
auto a2 = cr;//a2是int!虽然cr是low-level const,但是a2继承了cr引用的原对象的类型,而ci是top-level const
auto a3 = &i;//a3是pointer to int
auto a4 = &ci;//a4是pointer to const int
const auto a5 = ci;//a5是const int
我们也可以使用auto声明一个reference,但这样的话类型传递和上面稍有不同,即top-level const不会被忽略!
const int ci = 0;
auto &r = ci;//r是const int reference而不是plain int!
auto &r2 = 0;//error,不能将literal绑定给plain reference
const auto &r3 = 0;//ok,r3是const int reference

decltype

auto能让编译器自己决定使用哪个类型,但是在初始化的同时,程序也需要将等号右边的式子计算出来。有时候,我们希望程序不用计算右边的式子(节省开销),而只要返回类型即可,这时就要用decltype。
decltype返回的类型包括top-level const和reference
int func1(){}
const int func2(){}

decltype(func1()) a;//ok,a是int
decltype(func2()) b;//error!b是const int,需要赋初值

int i = 0;
int &r = i;
decltype(r) c;//error!c 是int&,是一个reference,需要指定绑定对象
decltype和reference
除了直接在decltype后的()内放入reference,还有其他情况也会导致返回reference。同时,也能将reference去掉。
int i = 0, &r = i,*p = &i;
decltype(r+0)a;//ok, 基本运算(+-*/%),比较运算(><==),逻辑运算(&&||!)导致返回int类型
decltype(r=0)b;//error!赋值运算依旧返回int&

decltype(*p) c;//error!*p返回int&

decltype(i) d;//ok,d是int
decltype((i))e;//error! 两个()永远返回reference!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值