您的位置:首页 > 编程语言 > C语言/C++

C++ Primer 学习笔记(第二章:变量和基本类型)

2015-08-20 09:48 603 查看
##C++ Primer 学习笔记(第二章:变量和基本类型)

[TOC]

###2.1 基本内置类型

wchar_t
(16位)类型用于确保可以存放机器最大扩展字符集中的任意一个字符,
char16_t
char32_t
则为
Unicode
字符集服务(
Unicode
表示所有自然语言中字符的标准)。

通常,
float
以1个字(32比特)来表示,
double
以2个字(64比特)来表示,
longdouble
以3或4个字(96或128比特)来表示。

整型类型(
int
short
long
longlong
)都是带符号(省略
signed
),加上
unsigned
就是无符号类型。
unsigned int
可以缩写为
unsigned


字符型有三种:
char
signedchar
unsigned char
。但字符型的表现只有两种,有符号和无符号,而char到底有没有符号要根据编译器决定。

类型选择经验:
(1)明确知晓不可能为负时选用无符号类型。
(2)使用
int
执行整数运算。
short
太小而
long
一般和
int
具有相同尺寸。如果范围超过
int
表示范围,再选用
longlong

(3)执行浮点数运算使用
double
。因为
float
通常精度不够,而双精度和单精度在计算代价上相差无几。
long double
一般情况下也无必要。

关于类型转换:当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模之后的余数;当赋给带符号类型一个超出它表示范围的值时,结果是未定义的(
undefined
)。

整型字面值:以0开头的整数代表八进制,以
0x
0X
开头的代表十六进制数。

字符字面值:
'A'
,只有一个单独字符;字符串字面值:
"A"
,有两个字符,一个
A
,一个空字符
'\0'


如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则他们实际上是一个整体。即当书写的字符串字面值较长,一行写不下,可采取分开书写的方式。

###2.2变量

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

C++11
新标准:用花括号初始化变量:

int unit=0;
int unit={0};
int unit{0};
int unit(0);

被称为列表初始化,无论是初始化对象还是某些时候为对象赋新值都可以用花括号。

如果内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0;定义在函数体内部的内置类型变量不被初始化,其值是未定义的(
undefined
)。

为了支持分离式编译,
C++
语言将声明和定义区分开来。如果想声明一个变量而非定义它,就在变量名前添加关键字
extern
,而且不要显式地初始化变量:

extern int i;//声明而非定义
int j;//声明并定义j

变量能且只能被定义一次,但是可以被多次声明。如果要在多个文件中使用同一个变量,就必须将定义和声明分离,变量的定义必须出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。定义在函数体外的标识符不能以下划线开头。

如果函数内部定义了一个与全局变量同名的新变量,则直接访问变量名时,局部变量将会覆盖全局变量;若想显式访问全局变量需要在变量名前使用作用域操作符(
::
)。(注:函数内部不宜定义相同变量名)

###2.3 复合类型(引用和指针)

定义引用时(
&d
),程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值绑定在一起。引用必须初始化。

引用并非对象,它只是一个已经存在的对象所起的另外一个名字,所以不能定义引用的引用。

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号
&
开头。

所有引用的类型要和绑定的对象严格匹配(仅是初始化时严格匹配,实际上引用也无法重新绑定到另一个对象上),且引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

指针和引用的区别:指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象(而引用非对象,一旦定义就无法绑定到另外对象上);指针无须在定义时赋初值。

指针的类型要和它指向的对象严格匹配。

指针的解引用符(
*
),如果给解引用的结果赋值,就相当于给指针所指对象赋值。

空指针的表示形式有三种:

int *p1 = nullptr;(推荐)
int *p2 = 0;
int *p3 = NULL;(#include<cstdlib>)


int
变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行。

两个指针存放的地址值相同有三种可能:
都为空;都指向一个对象;都指向同一对象的下一地址。
也有可能是一个指针指向某对象,另一指针指向另外对象的下一地址。

void*
是一种特殊指针类型,它可以存放任意对象的地址。
利用
void*
只能做:和别的指针比较;作为函数的输入输出;赋给另外一个
void*
指针。
不能直接操作
void*
指针所指的对象,因为我们并不知道它是什么类型,也无法确定它能做哪些操作。

指针是内存中的对象,像其他对象一样也有自己的地址,因此允许把指针的地址再存放到另一个指针当中(即
**
表示指向指针的指针)

引用不是对象,不能定义指向引用的指针;但指针是对象,所以存在对指针的引用:

int i = 42, *p;
int *&r = p;
r = &i;
*r = 0;


###2.4 const限定符(
const
constT*
constT&


const
对象一旦创建后其值不可改变,所以
const
对象必须初始化。可以运行初始化,也可编译初始化。

const int i = get_size();
const int i = 52;


默认情况下,
const
对象被设定为仅在文件内有效。当多个文件中出现了同名的
const
变量时,其实等同于在不同文件中分别定义了独立的变量。

只在一个文件中定义
const
,在其他多个文件中声明并使用它:对于
const
变量不管是声明还是定义都添加
extern
关键字,并只需定义一次。

extern const int i = fcn();//file.cc
extern const int i;//file.h

如果想在多个文件中共享
const
对象,必须在变量的定义之前添加
extern
关键字。

const T&
(只读引用):初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
const auto&
的作用是:避免对元素的拷贝;避免对象的写操作。

int i1 = 42;double i2 = 3.14;
const int &r1 = i1;
const in t&r2 = i2;

但当常量引用被绑定到另外一种类型上时,它只绑定到了一个临时量对象,如果改变被绑定对象或其另外一个同类型引用的值时(注意此处的值与常量引用的类型不同),常量引用的值将不会改变。而如果常量引用和绑定类型一致时,则常量引用可看成一个只读功能,也会对被绑定对象的值改变。
但是需要注意:要想绑定一个常量必须用常量引用。









尽管常量引用的初始化要求比较宽松,但不能忘记非常量引用严格要求类型匹配(表达式不行,常量同一类型也不行,必须是同类型变量)。其原因可以理解为:既然定义了非常量引用,就希望通过引用修改绑定对象的值,但实际上只要类型稍有不同就一定存在类型转换,存在类型转换就需要编译器临时创建一个临时量对象用于非常量引用的绑定,而这种绑定将导致对引用的修改无法反映到原始的对象上(只反映到临时量对象,而这个对象毫无意义,也无法访问)。所以
C++
禁止这种类型不用的引用绑定。

指向常量的指针(
const T*
)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针:

const double pi = 3.14;
const double *cptr = π//这里不能随随便便定义一个非常量指针double*来存放常量的地址

存放常量对象的指针必须使用指向常量的指针,但并不代表指向常量的指针不能指向非常量。(见第7条)

允许一个指向常量的指针指向一个非常量的对象:(也相当一个通过地址只读)

double dval = 3.14;
const double *cptr = &dval;

指向常量的指针也仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
指向常量的指针可以随时改变其所指对象,只要不改变值即可。

允许把指针本身定义成常量。常量指针(
T *const
)必须初始化,且一旦初始化完成,它的值(即存放的地址)就不能再改变了,即不变的指针本身的值(即存放的地址)而非指向的那个值。(但是可以通过常量指针修改所指对象的值)

int errNumb = 0;
int *const curRrr = &errNumb;
const double pi = 3.14159;
const double* const pip = π//指向常量对象的常量指针


指向常量的指针(
const T *name
Vs
常量指针(
T *const name
):
从右向左阅读,
const
离谁近,谁不能变(“谁”包括指向的地址和所指对象的值)。
const T1 * const T2
就都不能变。

顶层
const
和底层
const
:
顶层
const
表示指针本身是个常量,而底层
const
表示指针所指的对象是一个常量。一般的,顶层
const
可以表示任意的对象是常量,对任何数据类型都适用;而底层
const
和指针引用等复合类型的基本类型部分有关。而指针类型既可以有顶层
const
也可以有底层
const

当执行拷贝操作时,常量的顶层
const
不影响。而如果是底层
const
,拷入和拷出对象必须具有相同的底层
const
资格,或者两个对象的数据类型必须能够转换。一般来说非常量可以转化为常量。
即两条准则:(1)顶层
const
不影响拷贝;如果被拷贝的有底层
const
,必须具有相同的底层
const
资格才能拷贝;(2)非常量可以转化为常量,反之不行。
例:

int i= 0;
int *const p1 = &i;//顶层const
const int ci = 42;//顶层const
const int *p2 = &ci;//底层const
const int *const p3 = p2;//靠右的是顶层const,靠左的是底层const
const int &r = ci;//用于声明引用的都是底层const
//顶层const不影响拷贝:
i = ci;//ci是一个顶层const
//底层const:
int *p = p3;//错误,p3有底层const,而p没有
p2 = p3;//正确,p2和p3都有底层const,这里p3的顶层const不受影响
p2 = &i;//正确,非常量可以转化为常量
int &r = ci;//错误,普通的int&不能绑定到int常量上,即常量不可以转化为非常量
const int &r2 = i;//正确,相当于制度,即非常量可以转化为常量


常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式。字面值、用常量表达式初始化的
const
对象也是常量表达式。

const int max_files = 20;//是
const int limit = max_files + 1;//是
int staff = 27;//不是
const int sz = get_size();//不是


C++11
允许将变量声明为
constexpr
类型以便编译器来验证变量的值是否是一个常量表达式。声明为
constexpr
的变量一定是一个常量,而且必须用常量表达式来初始化。

constexpr int sz = size();//size()必须是一个constexpr类型函数。

一般来说,如果认定一个变量是常量表达式,那就把它声明成
constexpr
类型。

对声明为
constexpr
用到的类型称为“字面值类型”,这些类型一般比较简单,值也显而易见、容易得到。算数类型、引用和指针属于字面值类型。类、
IO
库、
string
不属于。

###2.5 处理类型

有两种方法用于定义类型别名:使用关键字
typedef
和使用别名声明。

typedef double wages;
typedef wages base, *p;//p是double*的同义词
using SI = Sales_item;//C++11


如果某个类型别名指代的是复合类型或常量:

typedef char *pstring;
const pstring cstr = 0;//cstr是指向char的常量指针

const pstring
是指向
char
的常量指针,而非指向常量
char
的指针。

C++11
引入
auto
类型说明符,让编译器通过初始值来推算变量的类型。而
auto
的变量必须有初始值。

auto
可以在一条语句中声明多个变量,但一条声明语句只能有一个基本类型。

auto
的初始值为引用时,参与初始化的是其引用对象的值;
auto
一般会忽略掉顶层
const
,同时底层
const
则会留下来:(即
auto
忽略引用,忽略顶层
const


int i = 0, &r = i;
auto a = r;//a是一个整数
const int ci = i, &cr = ci;
auto b = ci;//整型,忽略顶层const
auto c = cr;//整型,cr和ci无差别
auto d = &i;//整型指针
auto e = &ci;//指向整型常量的指针(对整数常量取地址是一种底层const)
//如果希望推断出的auto类型是一个顶层const则需要明确给出const:
const auto f = ci;
//也可以将引用类型设为auto,此时初始化规则仍然适用:
auto &g = ci;//g是引用,绑定到ci
auto &h = 42;//不能为非常量引用绑定字面值
const auto &j = 42;//可以为常量引用绑定字面值


希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量。
C++
定义了类型说明符
decltype
来选择并返回操作数的数据类型。此过程中编译器分析表达式并得到它的类型,却不实际计算表达式的值。

decltype(f()) sum = x;//sum的类型就是f函数返回的类型


auto
不同,如果
decltype
使用的表达式是一个变量,则
decltype
返回该变量的类型(包括顶层
const
和引用在内):

const int ci = 0, &cj = ci;
decltype(ci) x = 0;//x是const int
decltype(cj) y = x;//y是const int&,并绑定到x上
decltype(cj) z;//错误,z是一个引用,必须初始化

引用从来都作为其所指对象的同义词出现,只有用在
decltype
处是例外。

decltype
使用的表达式如果是一个解引用操作,则
decltype
将得到引用类型而不是解引用类型:

int i = 42, *p = &i, &r = i;
decltype(r + 0) b;//正确,加法的结果是int,b是一个未初始化的int
decltype(*p) c;//错误,c是int&,必须初始化


如果
decltype
使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式,而这样的
decltype
就会得到引用类型。

int i = 42;
decltype((i)) d;//错误,d是一个int&,必须初始化
decltype(i) e;//正确,e是一个未初始化int

decltype((variable))
的结果永远是引用,而
decltype(variable)
的结果只有当
variable
本身就是一个引用时才是引用。

表达式是引用的深层解释:
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说i是int,则表达式
i = x
的类型是
int &


###2.6 自定义数据结构

类(结构体
struct
)定义时,最好不要把对象的定义和类的定义放到一起。

struct Sales_data{/*...*/};
Sales_data accum, *saleptr;


C++11
新标准规定可以为数据成员提供一个类内初始值。创建对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化。

类通常定义在头文件中,而且类所在头文件的名字应与类的名字一样。
头文件通常包含那些只能被定义一次的实体,如类、
const
constexpr
变量等。

预处理器:确保头文件多次包含仍能安全工作的常用技术。
#include
:用指定的头文件内容代替
#include

头文件保护符:依赖于预处理变量。
预处理变量有两种状态:已定义和未定义。
#define
指令把一个名字设为预处理变量,
#ifdef
#ifndef
分别检查某个指定的预处理变量是否已经定义。
#ifdef
当且仅当变量已定义时为真,
#ifndef
当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直到遇到
#endif
指令为止。
例如某个头文件:

#ifndef SALES_DATA_H//第一次包含时为真,第二次为假不执行后面)
#define SALES_DATA_H
#include <string>
struct Sales_data{
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif

预处理变量包括头文件保护符必须唯一,通常的做法是基于文件中类的名字来构造,预处理变量的名字全部大写。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: