【c++primer第五版】第二章--变量和基本类型

目录

基本内置类型

变量

标识符

复合类型

const 限定符号

处理类型

decltype类型说明符

自定义数据结构

参考


基本内置类型

算术类型:字符,整型数,布尔值和浮点数,

空类型:不对应具体的值,用在特殊的场合;最常见的是函数不返回任何值;

注:位数是该类型数据所占的比特数。

比特:计算机以比特序列储存数据,每个比特是0或1;大多数计算机以2的整数次幂个比特来处理内存,可寻址的最小内存块称为字节(byte),存储的基本单元成为字(word),一般的,8比特=1字节;32或64比特 = 4或8字节 = 1字

大多数计算机将每个字节和一串数字(称为地址)联系起来:例如:

736424 (地址)

11011100

 (8比特具体内容)

数据类型决定了数据所占比特数以及如何解释这些比特的内容。

整型的带符号和不带符号的类型:带符号的类型可以表示正数,负数和0;不带符号的只能表示正数;bool类型和其他的扩展类型是没有带符号和不带符号的分别;int,short,long,long long 都是带符号的,可以在前面加上unsigned 表示无符号;

字符型带符号和不带符号类型:char,signed char,unsigned char, 有三种,但是表现形式只有两种(signed 和 unsigned),char  和 signed char 并不一样,具体那种形式和编译器有关。

类型转换:

unsigned char c = -1;    
赋值超出范围--结果是初始值对无符号类型的表示数值总数取模后的余数,8比特大小的unsigned char 表示0-255,则结果是-1/256 之后的余数,所以结果是255;

signed char c2 = 256;
赋值超出范围(-128-127)--因为是带符号的数据类型,所以结果未定义;程序可能崩溃,可能继续工作,可能成为垃圾数据;

含有无符号类型的表达式:

unsigned u = 10;
int i = -42;
std::cout << i + i << endl;    输出结果 -84

std::cout << i + u << endl;    输出结果 如果int 32位比特数 则 4294967264 
计算时,首先把-42 转化成无符号数,类似于直接给无符号数赋一个负值,结果是这个负数加上无符号数的模;

当从无符号数减去一个数时,不管这个数是不是无符号数,我们都必须确保结果不能是一个负值;
unsigned u1 = 42, u2 = 10;
std::cout << u1 - u2 << endl;    输出结果 32

std::cout << u2 - u1 << endl;    输出结果 一串数字

注意:不要混用带符号类型和无符号类型;

字面值常量:一个形如42的值被叫做字面值常量;字面值常量的形式和值决定类它的数据类型;o开头的整数代表八进制,ox或者OX开头的代表十六进制;

字符和字符串字面值 :‘a’:字符字面值;“a” ,“hello”:字符串字面值;字符串字面值实际上是常量字符构成的数组,编译器在每一个字符串的结尾处加空字符(‘\0’),所以字符串字面值比实际内容多一个;

字符串字面值比较长时,可以分开书写,如果字符串字面值位置紧邻,且仅有空格,缩进和换行符分隔,则它们是一个整体;

std::cout << "it is a vrey good idea"
              "she is a very good woman" << endl;

转义序列:略;

变量

对象(object):能储存数据并且具有某种类型的储存空间;

初始值:对象在创建的时候获得了一个值,这就是初始值,就说对象被初始化了;赋值和初始化时两个完全不同的操作,但是两者区别可以忽略不计;

赋值:把对象当前值擦除,以一个新的值来代替;初始化:创建变量时赋予其一个初始值;

默认初始化:默认值由变量类型和定义变量的位置来决定;

注意:建议初始化每一个内置类型的变量;未初始化的变量有一个不确定的值,使用未初始化的变量的值是一种错误的编程行为;

变量的声明和定义的关系

声明:使名字为程序所知;一个文件如果想使用别处定义的名字则必须包含对那个名字的声明;

定义:创建和名字关联的实体;

extern int i ;   声明i
int j;    声明并定义j
如果声明一个变量并不定义它,就在变量名前加extern 并且不要显式的初始化变量

extern int pi = 3;    定义pi    
任何包含了显式初始化的声明就会成为定义;extern 语句包含初始值就成为定义了

注意:变量只能被定义一次,但是可以被多次声明

注意:c++是静态类型语言,就是在编译阶段检查类型;

标识符

变量命名规范:

  1. 标识符要能体现实际含义;
  2. 变量名一般用小写字母;
  3. 用户自定义的类名用大写字母开头;
  4. 标识符若由多个单词组成,单词间应该有明显的区分;

名字作用域:作用域一般以{}分隔;分为全局作用域和块作用域;

main()函数和其他定义在函数体之外的变量一样用于全局作用域;定义在函数体之内的变量是块作用域;

注意:一般的在对象第一次被使用的地方定义它;

int rescure = 42;    --全局作用域
int main()
{
    int u = 1;    --块作用域
    int resure = 43;    --新建块作用域 覆盖全局变量 resure
    std::cout << resure <<endl;    --结果43
    std::cout << ::resure << endl;    --结果42  ::resure 显式的访问全局变量resure
}

复合类型

引用和指针

引用:引用(一般说的引用)--左值引用;

引用定义:为对象起另外一个名字;&d的形式来定义;

int ival = 1024;
int &refval = ival;    refval指向ival;
int &refval2;    报错,引用必须被初始化;
程序把引用和他的初始值绑定在一起,而不是拷贝(初始化时),初始化完成,将一直绑定在一起,无法重新绑定另外一个对象,所以引用需要初始化;引用是为已经存在的对象起另外一个名字;
一条语句可以定义多个引用
int i = 1024, i2 = 2048,i3 = 1;
int &r = i,r2 = i2;    r 是一个引用,r2 是int;
int &r3 = i3,&r4 = i2;    r3 r4 都是引用
注意:引用的类型和与之绑定的对象的类型必须严格匹配;引用只能绑在对象上;
int &reval = 10;    错误,引用只能绑定在对象上;
double dval = 3.14;
int &refval2 = dval;    错误,类型不匹配,必须是int 类型的对象;

指针:指向另外一种类型的复合类型;

指针与引用的区别:

  1. 指针本身是一个对象;允许对指针进行 赋值和拷贝;指针在生命周期内可以指向几个不同的对象;
  2. 指针无需在定义时赋初值,在块定义域内的指针,如果没有被初始化,也将拥有一个不确定的初值;
int *p1,*p2;    p1 和p2 是指向int 类型的指针;
获取对象的地址,用取地址符&;
int *p = &ival    p 存放变量ival的地址 或者说 p是指向变量 ival 的指针;
注意:引用不是对象,没有地址,所以不能定义指针指向引用;一般的,指针的类型要和它指向的对象类型严格匹配(特殊情况后面说);

指针值应该属于下列的状态之一:

  1. 指向一个对象;
  2. 指向紧邻对象所占空间的下一个位置;
  3. 空指针;
  4. 无效指针;

利用指针访问对象:*(解引用符)访问对象;

int ival = 42;
int *p = &ival;
cout << *P <<endl;    输出42
*p = 0; 对指针的解引用会得出所指的对象,即可由p为变量ival赋值
cout << *p <,endl;    输出 0     为*p 赋值就是在为 p 所指的对象赋值
符号的多重含义:* &
int i = 42;
int &r = i;    对 i 进行引用;& --引用符,紧随类型名出现,是声明的一部分
int *p;    定义指针;*是声明的一部分
p = &i;    指针 p 指向i; & --取地址符,出现在表达式中
*p = i;    将i的值赋给 p 所指向的对象;* --解引用符;出现在表达式中
int &r2 = *p;  对指针 P 引用 * --解引用符;&是声明的一部分,是引用符

空指针:不指向任何对象;

生成空指针的方法:
int *p = nullptr;    最常用
int *p2 = 0;
int *p3 = null;    需要 #include<cstdlib> --在头文件中,null = 0;
预处理变量不属于命名空间std,因此可以直接使用而不用加上std::;
建议:初始化所有指针;

注意:指针和引用都是对其他对象的间接访问。引用本身不是一个对象,一旦定义引用,就无法令其在绑定到另外的对象,每次使用都是访问它最初绑定的对象;

但是指针和地址之间没有这种限制,给指针赋值,就是给他存放一个新的地址,从而指向一个新的对象;

注意:赋值改变的是等号左侧的对象;

int *pi; 
pi = &ival;    pi的值被改变 pi 指向了ival;
*pi = 0;       ival 的值被改变,指针pi并没有被改变;给指针指向的对象赋值
注意:任何非0的指针的条件值都是true

void*是特殊的指针类型,可以存放任意类型对象的地址;

复合类型的声明

一条定义语句可以定义出不同类型的变量
int i = 1024, *p = &i, &r = i;
指向指针的应用
int i = 42, *p;
int *&r = p;    r是一个指针,对指针 p 进行引用;
r = &i;    对指针r进行赋值,指向变量 i ;
*r = 0;    对指针r进行解引用,将 i = 0; 
注意:面对复杂的声明语句,从右向左阅读 r 的定义

const 限定符号

const 对象一旦创建之后其值就不能再改变,所以const 对象必须初始化;

const初始化
const int i = get_size();    正确--运行时初始化
const int j = 42;    正确--编译时初始化
const int k;    错误,k没有初始化

const  被设定为仅在文件内有效,当文件中出现同名的const变量时,等同于不同文件中分别定义了独立的变量;

如果定义一个const对象,想在多个文件中共享,就需要声明它,加 extern关键字;

const 引用:把引用绑定到const对象上,称为对常量的引用,但是记得不能用作修改它所绑定的对象;

const int ci = 1024;
const int &r1 = ci;    正确,引用及其对应的对象都是常量
r1 = 42;            错误,r1 是对常量的引用,
int &r2 = ci;     错误,r2不是常量
注意:因为不允许直接为ci赋值,也就不能通过引用去改变ci的值;对const的引用,简称:对常量的引用,
(严格来讲,并不存在常量引用,因为引用不是一个对象,所以没有办法让引用本身固定不变,因为c++不允许改变引用所绑定的对象,所以从这里来看,所有引用又都算是常量)

引用类型必须与所引用对象的类型一致,但是有两种例外:

  1. 初始化常量引用时允许用任意表达式作为初始值,只要是该表达式的结果能够转换成引用的类型即可,允许一个常量引用绑定非常量的对象,字面值,或者一般的表达式;
int i = 42;
const int &r1 = i;    --true 允许将const int & 绑定到一个普通的int身上
const int &r2 = 42;    --true r1 是一个常量引用,允许绑定字面值
const int &r3 = r1 * 2;    --true r3 是常量引用
int &r4 = r1 * 2;    --false r4 是一个非 常量引用 
int i = 42;
int &r1 = i;    --引用r1 绑定i
const int &r2 = i;    --r2绑定对象i 但是不允许通过r2修改i的值(重要)
r1 = 0;    --r1不是常量,i 的值修改为0
r2 = 0;    --r2是常量,无法修改,错误

指针和const

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

const double pi = 3.14;
double *ptr = &pi;    -- false ptr 是普通指针
const double *ptr2 = &pi;    --true ptr2 是指向常量的指针,可以指向pi
*ptr2 = 42;      --false 不能赋值
注意:指向常量的指针没有规定指向对象必须是一个常量,仅仅要求:不能通过该指针改变对象的值,而没有规定那个对象的值能不能通过别的途径改变;
(这样想:指向常量的指针或者引用,不过是他们自以为是,自觉地不去改变所指对象的值)
--常量指针 必须初始化 一旦初始化完成,它的值不允许改变;
int err_numb = 0;
int *const cur_err = &err_numb;    -- 常量指针 cur_err 将会一直指向 err_numb;
(从右向左,cur_err左边是const ,说明 它是一个常量对象,之后是*,说明他是常量指针,最后,发现常量指针指向一个int对象,同理下面的pip是一个指向双精度浮点型常量类型的常量指针)

const double pi = 3.14159;    
const double *const pip = &pi;    --pip 是指向常量对象的常量指针;

注意:指针本身是一个常量不意味着不能通过指针修改其所指定的对象,能否这样做取决于 所指对象 的类型,cur_err所指对象是非常量,那么可以通过cur_err去修改err_numb的值:
if(*cur_err)
    error_handler();
    *cur_err = 0;    --true

顶层const

指针本身是一个对象,他又可以指向另一个对象。因此,指针本身是不是常量和指针所指的对象是不是常量的问题是两个独立的问题。

顶层const 表示指针本身是个常量;底层const 表示指针所指对象是一个常量。

顶层const 可以表示任意对象是常量,对任意数据类型都适用;底层const 与指针和引用等符合类型的基本类型部分有关。特殊的是指针类型既可以是顶层const,也可以是底层const.

int i = 0;
int *const  p1 = &i;    -- 不能改变p1的值,顶层const
const int ci = 42;  --不能改变ci的值,顶层const  
const int *p2 = &ci;    --允许改变p2的值,底层const

const int &r = ci;    --用于声明引用的const 都是底层const

constexpr和常量表达式

常量表达式:值不会改变并且在编译过程中就能得到计算结果的表达式;由数据类型和初始值共同决定;

const int s_z = get.size();    --不是常量表达式,get.size() 直到运行使才能获取到;

constexpr变量:声明为constexpr 类型,以便由编译器来验证变量的值是否是一个常量表达式;一般地,如果你认定变量使一个常量表达式,你就把它声明成constexpr 类型;

constexpr 类型的指针的初始值必须是:nullptr;0;或者存储与某个固定地址中的对象;constexpr 仅与指针有关,与指针所指的对象无关;

处理类型

类型别名:一个类型名字,让复杂的类型名字变得简单;

两种方式:typedef,using

using si = sales_item;    --si是sales_item的别名

typedef double wages;    --wages 是double 的名字

auto类型说明符:auto让编译器通过初始值来推算变量的类型;auto 能在一条语句中声明多个变量,但是一条声明语句只能由一个基本数据类型,所以该语句中的所有变量的初始基本数据类型都必须一样;

auto i = 0,*p = &i;    --true 类型相同
auto sz = 0,pi = 3.0;    --false 类型不同

decltype类型说明符

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

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

decltype 处理顶层const 和引用的方式与auto 不同。如果decltype 使用的表达式是一个变量,则decltype 返回该变量的类型:
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;      --false z是引用,必须初始化

cj 是引用, decltype (cj)是引用类型。引用从来都是作为所指对象的同义词出现,只有在decltype 例外。
int i = 42,*p =&i,&r = i;
decltype(r+0) b;    --true 加法结果是int,b 是int 类型
decltype(*p) c;    --decltype(*p) 是int& 类型,需要初始化

decltype((i)) d;    --d 类型是int& 需要初始化
decltype 使用的是不加括号的变量,得到的是该变量的类型,如果加上一层或多层括号,编译器会把它当成一个表达式,变量是一种 可以作为赋值语句左值的特殊表达式,所以得到引用类型。

自定义数据结构

struct 关键字;

struct sale_date
{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

类内初始值将用于初始化数据成员,没有初始值的成员会被默认初始化;

编写自己的头文件:类一般被定义在头文件中,类所在头文件的名字应该和类的名字一样;

预处理器:确保头文件多次包含仍能安全工作的技术;

头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义和未定义;

#define -- 把一个名字定为预处理变量; #ifndef -- 当且仅当变量未定义时为真;#ifdef --当且仅当变量已定义时为真;一旦检查结果为真,则执行后续操作指导#endif 为止;

注意:一般习惯性加上头文件保护符就可以;

参考

《c++primer》第五版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值