变量和基本字节

1. 原始内置类型

C++中包含了两种原始类型:

  1. 算术类型(arithmetic types)
  2. 特殊类型void :很少用,一般用作函数无返回值。

1.1 算术类型

算术类型分为两类:整数类型(integral types)浮点类型(floating-point types)。在不同的机器上,整数类型占的位是不同,最小的位数要求如下表:
来自于C++ Primer,Fifth Edition
对于整数类型来说,除了bool和扩展的字符类型(wchar_t, char16_t, char32_t),整数类型还分为有符号(signed)无符号(unsigned)。:

  1. 有符号intshortlonglong long
  2. 无符号:在类型前加 unsigned

注:对于char类型分为:charsigned charunsigned char,但是一般char与后面两个类型之一相同,由编译器决定。一般一个8-bit的unsigned char能够保存值的范围是0-255

对于用什么类型有些规则:

  1. 当知道值是非负的话用unsigned 类型
  2. 对于整型,用int,当数据值太大时,直接用long long,因为实际情况下long的大小会与int一样。
  3. 在算术表达式中不要使用charbool
  4. 对于浮点计算的话用double

1.2 类型转换

一些类型支持的运算可以进行类型的转换

bool b=42;		//b is true
int i=b;		//i=1
i=3.14; 		//i=3
double pi=i;	//pi=3.0
unsigned char c=-1;		//假定8-bit chars,c=255
signed char c2=256;		//假定8-bit chars,c2的值不确定


//Converting a negatve number to unsigned 
unsigned u=10;
int i=-42;
std::cout<<i+i<<std::endl;	//	prints -84
std::cout<<u+i<<std::endl;	//  if 32-bit ints, prints 4294967264

对于上面的转换,当我们给一个无符号类型的变量赋值时,如果值超过其范围,变量保存的结果就是将原始值对目标类型所能保存的值求余。例如上面的 c 保存的值就是255(-1%256)。当我们给一个有符号的变量赋值时,如果值超过其范围,变量保存的结果就未定义了,会产生不确定性。在使用unsigned类型要特别小心,特别是在循环中,防止出现意外。

2. 变量

变量在定义初始化的方法中有个列表初始化的形式,但是对于内置类型的变量在使用列表初始化有个限制:编译器不允许缩窄转换的初始化。

long double ld = 3.1415926536;
int a{ld}, b={ld}; 	//error:narrowing conversion required
int c(ld), d=ld;	//ok:value will be truncated

为了能够分开编译,C++区分了声明定义

  1. 声明:利用extern关键字让程序知道这个变量,指定了变量的类型和名字。
  2. 定义:创建一个相关联的实体,变量的定义就是一个声明,定义还分配内存以及给变量初始值。
extern int i;	//声明但没有定义 i
extern double pi = 3.1416; 	//定义。但不允许在函数内部给extern进行初始化
int j;			//声明并定义了

对于内置型类型,在全局区域没有初始化会被初始化为0,在局部区域没有初始化有未定义值。

3. 复合类型

3.1 引用

引用为对象定义了一个别名。

int ival = 1024;
int &refVal = ival;	// refVal refers to ival

引用必须初始化,初始化后引用绑定到初始化对象,无法改变去绑定其他对象。引用不是一个对象,不能定义为一个引用去定义一个引用。

3.2 指针

指针是指向另一种类型的复合类型,间接的获得其他对象,与引用不同的是,指针是个对象,能够赋值和复制,指针能指向不同的对象,在定义时可以不进行初始化。

double dval;
double *pd = &dval;	//ok:初始化是一个double的地址
double *pd2 = pd;	//ok:初始化是一个指向double的指针

存储在指针中的值有四个形式:

  1. 指向对象。
  2. 指向对象的末尾。
  3. 空指针。
  4. 除了上面三种的无效值。
int ival = 1024;
int *pi = 0;	//pi is a valid, null pointer, nonzero pointer
int *pi2 = &ival;	//pi2 is a valid pointer that holds the address of ival
if (pi)	//pi has value 0, so condition evaluates as false
if (pi2)	//	pi2 points to ival, so it is not 0; the condition evaluates as true

//对于相同类型的两个有效指针,当它们的地址一样两个指针相等,否则不相等。

void *Pointers 是一个特殊的指针类型能够保存任何对象的地址。还有需要注意的就是

3.2.1 指针的指针:

int ival = 1024;
int *pi = &ival;	//pi points to an int
int **ppi = &pi;	//ppi points to a pointer to an int


牢记一点:指针保存的是地址。

3.2.2 指针的引用

因为指针是个对象,所以可以定义一个指针的引用。

int i = 42;
int *p;
int *&r = p;	//r is a reference to the pointer p, beacuse the type of reference is required same with objective, so int * is represent the type of reference.
r = &i;	//	r refers to a pointer; assigning &i to r makes p point to i
*r = 0; 	//changes i to 0

我们可以通过从右到左读定义来理解r的类型。&与r组合&r,得到r是个引用,剩余部分就是r引用的类型,* 说明r引用的是一个指针类型,最终这个声明就是r是一个指向int类型的指针的引用。

4 const限定词

我们可以通过const让变量不可改变。在创建const对象时必须进行初始化,否则报错,在编译时const对象被常量初始化,编译器会将变量直接替换成对象的常量值,因此为了编译器需要看到变量的初始化,但是当一个程序分为多个文件,每个利用const的文件就都需要看到它的初始化,即变量要定义在每个文件中,为了满足这种需求,同时避免多个相同变量的定义,const变量是被定义为文件本地的。当我们想在多个文件共享一个const 变量,利用关键词extern在它的定义和声明上。

//file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
//file_1.h
extern const int bufSize; //same bufSize as defined in file_1.cc

4.1 const引用(references to const)

const的引用不能改变它所绑定的对象。

const int ci = 1024;
const int &r1 = ci;	//ok:both reference and underlying object are const
r1 = 42;	//error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object
//因为我们不能直接给ci赋值,因此我们也不能利用引用去改变ci

一般情况下,引用的类型必须与引用的对象类型一致,但是有两个特例:

  1. 我们能够用任何表达式初始化const引用,只要表达式能转换为引用的类型。
int i = 42const int &r1 = i;	// we can bind a const int& to a plain int object
const int &r2 = 42;	// ok: r1 is a reference to const
const int &r3 = r1*2;	// ok: r3 is a reference to const
int &r4 = r * 2; // error: r4 is a plain, non const reference

通过下面一个例子仔细的探究发生了什么:

double dval = 3.14;
const int &ri = dval;
//ri是int的引用,而dval是个浮点数,为了确保ri绑定到int对象,编译器进行类型如下转换
const int temp = dval;	//	create a temporary const int from the double
const int &ri = temp;	// bind ri to that temporary
  1. 当ri不是const会如何?如果能够进行初始化,因为ri不是const,所以可以进行赋值,程序员这样就能通过ri改变所绑定对象的值,但是这里实际绑定的是一个临时变量,并非程序员所想的dval对象,所以会被判定为非法语言。

特别提醒:一个const引用仅仅限制我们通过引用所做的操作,将const的引用绑定到对象不会说明基础对象本身是否为const。

int i = 42;
int &r1 = i;	// r1 bound to i
const int &r2 = i;	// r2 also bound to i; but cannot be used to change i
r1 = 0;		// r1 is not const; i is now 0
r2 = 0;		// error: r2 is a reference to const

4.2 指针和const

我们可以定义指向const或者non const类型的指针。
一般情况下,指针的类型要和所指对象的类型保持一致。

const double pi = 3.14;
double *ptr = &pi; // error: ptr is a plain pointer
const double *cptr = &pi; // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr

我们能够利用pointer to const的指针去指向一个non const类型的对象:

double dval = 3.14; // dval is a double; its value can be changed
cptr = &dval; // ok: but can't change dval through cptr

就像对const的引用一样,指向const的指针并没有说明指针所指向的对象是否为const。只影响通过指针所做的操作。
与引用不同,指针是个对象,我们能够对指针做const,就和其他的const对象一样,一个const pointer必须被初始化,一旦被初始化后,它的值(所保存的地址)就不能再改变。

int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = &pi; // pip is a const pointer to a const object

5 constexpr和常量表达式

常量表达式:值不能够改变,在编译时就能得出。我们可以通过constexpr关键字去声明一个变量,要求编译器确认这个变量是个常量表达式。

const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression

constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function

当我们在constexpr声明中定义指针时,constexpr说明符将应用于该指针,而不是该指针所指向的类型。

const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int

6 处理类型

6.1 类型别名

两种方式:typedef或者using =
注意指针,常量和类型别名混在一起时的情况

typedef char* pstring;	//an alias for the type char*
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char

上面代码中的const pstring是一个指向char的constant指针。并不是一个指向const char的指针。

6.2 auto和decltype

auto可以告诉编译器从初始化值推出类型
对于引用,实际用的是引用的对象进行推导,当我们使用引用作为初始项时,初始项是对应的对象。

int i = 0, &r = i;
auto a = r; // a is an int (r is an alias for i, which has type int)

对于const类型,auto通常忽略top-level const(本身是const),而low-level const(指向const对象的指针)会推出:

const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
//想让推出的类型有top-level const,必须声明
const auto f = ci; // deduced type of ci is int; f has type const int

decltype 返回其操作数的类型

decltype(f()) sum = x; // sum has whatever type f returns

对于和const的用法与auto不一样,返回的类型包括top-level const和引用:

const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized

我们也可以对表达式用decltype,得到表达式的类型,有些表达式可能让decltype产生一个引用类型:

// decltype of an expression can be a reference type
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized

上面代码中,当我们解引用一个指针时,我们得到指针所指的对象,更重要的是我们能够给这个对象赋值,因此推出的类型是int &
注意

// decltype of a parenthesized variable is always a reference
decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int

参考来源

  1. C++ Primer, Fifth Edition
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值