c++primer第二章变量和基本类型

2.1 基本内置类型

2.1.1算术类型
算术类型分两类:整型(integral type)和浮点型。
算术类型的尺寸在不同机器上有所差别。某一类所占的比特数不同,它所能表示的数据范围也不样。

类型 最小尺寸
bool 未定义
char 8位
wchar_t 16位
char16_t 16位
char32_t 32位
short 16位
int 32位
long 32位
long long 64位
float 6位有效数字
double 10位有效数字
long double 10位有效数字

基本的字符类型是char,一个char的大小和一个机器字节一样。
大多数计算机以2的整数次幂个比特作为块来处理内存,可寻址的最小内存块称为“字节”,存储的基本单元称为“字”,它通常由几个字节组成。大多数机器的字节由8比特组成,字则由32或64比特组成,也就是4或8字节。
为了赋予内存中某个地址明确的含义,必须首先知道存储在该地址的数据的类型。类型决定了数据所占的比特数以及该如何解释这些比特的内容。

无符号类型
无符号类型仅能表示大于等于0的值。
带符号类型
int short long 和longlong都是带符号类型。
字符类型
分为char signed char unsigned char。无符号中所有比特都用来存储值,例如,8比特的unsigned char 可以用来表示0至255之间的数

执行浮点数运算选用double,这是因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。

2.1.2 类型转换
大多数类型的对象支持类型转换
当在程序的某处我们使用了一种类型其实应该取另一种对象时,系统会自动进行类型转换。
例如:
bool b=41;//b为真
int i=b;//i的值为 1
i=3.14;//i的值为3
double pi=i;//pi的值为3.0
unsigned char c=-1;//假设char占8比特,c的值为255
signed char c1=256;//假设cahr占8比特,c1的值是未定义的

当我们把非布尔类型的算术值赋给布尔类型是,初始值为0则结果为false,否则结果为1;

把布尔赋给非布尔时,初始值为false为0,true时为1;

把浮点型赋给整型时,结果只保留浮点数中小数点之前的部分。

把整型赋给浮点型时,小数部分记为0.如果该整数所占空间超过了浮点类型的容量,精度可能有损失。

当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。

当我们赋给一个带符号类型一个超出它表示范围的值是,结果是未定义的。

如果表达式中既有带符号的又有无符号的,当带符号取值为负时会出现一场结果,这是因为带符号数自动转换成无符号数。例如在一个形如a*b的式子中,如果a=-1,b=1,而且a和b都是int,则表达式值为-1,但是如果b是unsigned,则结果在我们的环境里是4294967295.

2.1.3 字面值常量
一个形如42的值被称为字面值常量。每个字面值常量对应一种数据类型。

整型和浮点型字面值
以0开头的是八进制数,以0x或0X开头的是16进制。例如我们用不同的方法表示20:
20//十进制 024//八进制 0x14//十六进制
默认情况下十进制是带符号的,八进制和十六进制可能是带符号也可能是不带符号的。

字符和字符串字面值

‘a’//字符字面值
“hollow world”字符串字面值

编译器在每个字符串结尾处添加一个空字符(‘\0’),因此字符串字面值的实际长度要比他的内容多1。

转义序列
转义序列均以反斜线作为开始,c++语言规定的转义序列包括:

换行符\n       横向制表符\t      报警符\a
纵向制表符\v   退格符\b          双引号\"
反斜线\\       问号\?           单引号\'
回车符\r       进纸符\r
                                                  指定字面值的类型
前缀                   含义                                             类型
u                     Unicode16字符                                     char16_t
U                     Unicode32字符                                     char32_t
L                     宽字符                                            wchar_t
u8                    UTF-8                                             char
后缀                   整型字面值                                      浮点字面值            类型
u or U                unsigned                                        f or F               float
l or L                long                                            l or L               long double
ll or LL              long long  
           

布尔字面值和指针字面值
true和false是布尔类型的字面值:
bool test=false;
nullptr是指针字面值。

2.2 变量

2.2.1 变量定义
类型说明符紧跟一个或多个变量名组成的列表,其中变量名以逗号分割,最后以分号结束。
int sum=0,value, sold=0;

Sale_item item;//item是Sale_item类型

string book(“0-201-78345-X”);//book 通过一个string字面值初始化

通常情况下,对象是指一块能存储数据并具有某种类型的内存空间。

初始值

对象创建时被赋予一个特定的值,我们说这个对象被初始化了。初始化可以是任意复杂的表达式。
double price=3.14, discount=price*0.98;
double saleprice=函数返回值;

列表初始化
无论是初始化对象还是为某些对象赋新值。
int sold=0;
int sold={0};
int sold(0)
int sold{0};
花括号初始化仅仅在某些受限的场合下才能使用

long double ld=3.1415926536;
int a{ld},b={ld};//错误转换为执行,存在丢失信息的风险
int c(ld),d=ld;//正确,确实丢失了部分值

默认初始化
1.如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。
2.定义域任何函数体之外的变量被初始化为0,。

一个未初始化的变量的值是未定义的,如果试图拷贝或者以其他形式访问此类型将引发错误。建议初始化没一个内置类型的变量。

2.2.2 变量声明和定义的关系

c++语言支持分离式编译机制。该机制允许将程序分割为若干个文件,每个文件可独立编译。
声明是的名字为程序所知。定义负责创建与名字关联的实体。

如果想声明一个变量而非并非定义它,就在该变量前添加关键字extern,而不要显式的初始化变量:

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

变量能且只能被定义一次,但能可以被多次声明。

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

2.2.3标识符
c++的标识符有字母,数字下划线组成,其中必须以字母或下划线开头。标识符的长度没有限制,但是对大小写字母敏感:
int somename,someName,SomeName,SOMENAME;

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

重要命名规范
1.标识符要能体现实际含义。
2.变量名一般用小写字母,如index,不要使用Index或INDEX。
3.用户自定义的类名一般以大写字母开头,如Sale_item。
4.如过标识符有多个单次组成,则单次间应有明显区分,图student_loan或StudentLoan,不要使用studentloan

名字的作用域
名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。

一般来说,在对象第一次使用附近定义它是一种好的选择,因为这样更容易地找到变量的定义。

嵌套作用域
作用域能彼此包含,被包含的作用域称为内层作用域。包含着别的作用域的作用域称为外层作用域。
作用域中一旦声明某个名字。它所嵌套这的所有作用域中都能访问该名字。同时允许在内城定义域中重新定义外层作用域已有的名字。

2.3复合类型
复合类型是指基于其他类型定义的类型。
一条简单的声明语句有一个数据类型和紧随其后的一个变量名列表组成。每个声明符命名了一个变量并指定该变量与基本数据类型有关的某种类型。

2.3.1引用
引用为对象起另一个名字。

int ival=1024;
int &refval=ival;//refval指向ival
int &refval2;//错误引用必须初始化

引用是和他的初始值绑定在一起,而不是将初始值拷贝给引用。引用无法重新绑定到另一个对象,所以必须初始化。

定义了引用后在其上的所有操作都是在与之绑定的对象上进行的。

refal=2;
int li=refal;//等价于li=ival

int &refal2=refal;//正确refal3绑定到了与refval绑定的对象上

int i=refval;//正确,此时i被初始化为refval的值

允许在一条语句定义多个引用

int i=12, i1=55;
int &a=i,&b=i1;

引用只能绑定在对象上,不能与字面值或某个表达式的结果绑定在一起。
引用的类型必须和绑定的类型相同

double  i=12;
int &refval=i;//错误,类型不一致

2.3.2指针
指针是指向另外一种类型的复合类型。指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。指针无需再定义时赋值。

int *p,*q;//p q是指向int型对象的指针。

获取对象的地址
要想获得某个对象的地址,需要使用取地址符&

int ival=42;
int *p=&ival;//p存放变量ival的地址,或者说p指向ival;

除了两种例外,指针要与指向的对象的类型严格匹配。

指针值
指针的值应属下列4中状态之一:
1.指向一个对象。

2.指向紧邻对象所斩空间的下一个位置

3.空指针

4.无效指针,也就是上述之外的其他指针。

利用指针访问对象
如果指针指向一个对象,允许使用解引用符*来访问该对象:

int ival=42;
int *p=&ival;
cout<<*p;//输出平指向对象ival的值

空指针
空指针不指向任何对象。

int *p1=nullptr;
int *p2=0;
int *p3=NULL;//等价于*p3=0;

建议初始化所有指针

赋值和指针
指针本身就是一个对象,给指针赋值就是令它存放一个新地址,从而指向一个新对象:

int i=42;
int *p1=0;
int *p2=&i;//初始化p2,存有i的地址
int *p3;//如果p3定义域块内,那么p3的值是无法确定的
p3=p2;//p3和p2指向同一个对象i
p2=0;//现在p2不指向任何对象

又是后不好弄清楚到底改变的是指针的值还是指针所指对象的值,最好的方法是记住赋值永远改变等号左边的值。

其他指针操作

int ival=1024;
int *p=0;
int *p2=&ival;
if(p1)//条件为false
	//.....
if(p2)//条件未true
	//....

void 指针*
void* 指针是一类特殊的指针,可用于存放任意对象的地址。我们队该地址中到底是个什么类型的对象并不了解。

double obj=3.14,*pd=&obj
void *pv=&obj;//obj可以是任意类型对象
pv=pd;

不能直接操作void指针所指的对象。
以void
指针的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。

2.3.3理解复合内省的声明
一条语句可以定义不同类型的变量

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

定义多个变量
有一种错误观点,在定义语句时,类修饰符(&和*)作用域本次定义的全部变量。

int* p1,p2;//p1是指针p2是int型变量

指向指针的指针
指针是内存中的对象,想其他对象一样也有自己的地址,因此允许把指针的地址在存到另一个指针当中。

通过*的个数可以区分指针的级别。

int ival=1024;
int *p1=&ival;
int **p2=&p1;//p2指向一个int型指针
cout<<ival<<*p1<<**p2<<endl;//三种方式输出ival

指向指针的引用
不能定义指向引用的指针,能定义指向指针的引用。

int i=42;
int *p;
int *&r=p;//r是一个对指针p的引用
r=&i;//r引用了一个指针,因此给r赋值&i就是令p指向i
*r=0;//解引用r得到i,也就是p指向的对象,将i的值改为0

要理解 r的类型到底是什么最简单的方法是从右向左阅读 r的定义 ,离变量名最近的符号对变量有最直接的影响 ,因此 r是一个引用,声明符的其余部分可以确定 r引用的类型是什么。此例子中的符号 * 说明 r引用的是一个指针。

2.4 const限定符
可以用const来定义一个值不能改变的变量。

const int bufsize=512;
bufsize=512;//错误:试图对const对象写值

const对象必须初始化
const int k;//错误,k是一个未经初始化的常量

初始化和const
如果用一个对象去初始化另一个对象,那它们是不是const都无关紧要:

int i=42;
const int ci=i;
int j=ci;

ci的常量特征仅仅在执行改变ci的操作时才会发挥作用。

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

有时我们想在一个文件中定义const,而在多个文件中声明并使用它。解决方式是,对于const变量不管是声明还是定义都添加extern。

//file_1.cc
extern const int bufsize=512;
//file_1.h
extern const int buffsize;//与file_1.cc中是同一个

2.4.1 const的引用
可以吧引用绑定到const对象上,称之为对常量的引用 简称常量引用
常量引用不能用作修改它所绑定的对象:

const int ci=1024;
const int &r1=ci;
r1=42;//错误r1是常量引用
int &r2=ci;//错误试图让一个非常量引用指向一个常量引用

初始化和对const的引用
1.初始化常量引用是允许任意表达式作为初始值,只要改表达式的结果能转换成引用的类型即可。
2.允许为一个变量引用非常量的对象,字面值,甚至一般表达式。

int i=42;
const int &r1=i;
const int &r2=42;
const int &r3=r1*2;
int &r4=r1*2;//错误:r4是一个普通的非常量引用

常量引用和引用对象的类型必须匹配

double dval=3.14;
const int &r1=dval;//错误

对const的引用可能引用一个并非const的对象
常量引用仅对引用可参与的的操作做出了限定,对于引用的对象本身是不是一个变量未做限定。

int i=42;
int &r1=i;
const int &r2=i;
r1=0;
r2=0;//错误,不能通过r2改变i的值

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

const double  pi=3.14;
double *ptr=&pi;//错误:ptr是一个普通指针
const double *cptr=&pi;
*cptr=42;//错误不能给*ptr赋值

允许一个指向常量的指针指向一个非常量对象:

double dval=3.14;
cptr=&dval;

const指针
常量指针必须初始化,而且一旦初始化了,则它的值就不能改变了

int errnumb=0;
int &const curerr=&errnumb;//curerr将一直指向errnumb
const ouble pi=3.14159;
const double *const pip=&pi;//pip是指向一个常量对象的常量指针

顶层const
用名词顶层const表示指针本身是个常量,而用名词底层const表示指针所指的对象是一个常量

int i=0;
int *const pi=&i;//这是一个顶层cosnt
const int ci=42;
cosnt int *p2=&ci;//这是一个底层const

2.4.4 constexpr和常量表达式
常量表达式是指不会改变并且在编译过程就能得到结果的表达式。

一个对象是不是常量表达式由它的数据类型和初始值共同决定:

const int max_files=20;//max_files是常量表达式
const int limit=max_files+1;//常量表达式
int staff_size=25;//不是常量表达式

constexpr对象
允许将变量声明为constexpr类型以便于编译器来验证变量的值是否是一个常量表达式。

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

指针和constexpr对象
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效

constexpr int *p=nullptr;//p是一个指向整数的常量指针

2.5处理类型

2.5.1 类型表明
类型别名是一个名字,他是某种类型的同义词。

1.typedefdef

typedef double wages;//wages是double的同义词
typedef wages base,*p;//p是double*的同义词

2.使用别名生命来定义类型的别名

using SI=Sales_item;//SI是Sale_items的同义词

using作为别名声明的开始,把等号左侧的名字规定为等号右侧类型的别名

指针,常量和类型别名

typedef char *pstring;
const pstring cstr=0;//cstr是指向char的常量指针
const pstring *ps;//ps是一个指针,它的对象是指向char的常量指针

pstring实际上是指向char的指针,因此const pstring就是指向char的常量指针,而非指向常量类型的指针

const char *cstr=0;//是对 const pstring cstr=0的错误理解

2.5.2 auto类型说明符
c++引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。
auto类型说明符必须有初始值

auto item=val1+val2;//item初始化为val1和val2相加的结果

auto也能在一条语句声明多个变量。

auto i=0,*p=&i;//i是整数,p是整型指针

复合类型常量和auto
编译器推断出来的auto类型有时候和初始值的类型并不一样,编译器会适当的改变结果类型使其更符合初始化规则。

int i=0,&r=i;
auto a=r;//a是一个整数

auto一般会忽略顶层const但是会保留底层const:

const int i,&cr=ci;
auto b=ci;//b是整数
auto c=cr;//c是整数
auto d=&i;//d是整数指针
auto e=&ci;//e是指向整数常量的指针

2.5.3decltype类型指示符
希望从表达式的类型推断出要定义的变量的类型,但是不想用该变量的值初始化变量,c++第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。

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

如果decltype使用的是一个变量表达式,则decltype返回该变量的类型

const int c0,&cj=ci;
decltype(ci) x=0;//x是const int 类型
decltype(cj)y=x;//y的类型是const int &

decltype和引用
有些表达式将想decltype返回一个引用类型:

int i=42,*p=&i,&r=i;
decltype(r+o) b;//b是一个int类型
decltype(*p) c;//错误c是int&,必须初始化

因为r是一个引用,因此decltype(r)的结果是引用类型,如果想让结果是r所指的类型,可以吧r当做表达式的一部分。

如果表达式的内容是解引用操作,则decltype将得到引用类型。

解引用可以得到指针所指的对象,而且还能给这个对象赋值因此decltype(*p)得到的结果类型就是int&而非int;

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

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

预处理概述

预处理器是在编译之前执行的一段程序,可以部分的改变我们所写的程序。
c++还可能用到的一项功能是头文件保护符,头文件保护符依赖于预处理变量。

预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量,另外两个指令分别检查某个指令的预处理变量是否定义:#ifdef当且仅当变量已定义时为真,#ifdef当且仅当变量未定义时为真,一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。

#ifdef SALES_DATE_H
#define SALE_DATE_H
#include<string>
struct Sale_date{
	string bookNo;
	undigned units_sold=0;
	double revenue=0.0;
}
#endif

整个程序中的预处理变量包括头文件保护符必须唯一,通常的做法是基于头文件的类名来构建保护符的名字,以确保其唯一性。一般为了避免与程序中的其他实体发生名字冲突,预处理变量的名字全部大写。

猜你喜欢

转载自blog.csdn.net/l15271214563/article/details/83997550