第二章 变量和基本类型

第一节 基本内置类型

C++标准规定了算术类型尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。

比如:

类型 含义 最小尺寸
bool 布尔类型 未定义
wchar_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位

基本的字符类型是char,一个char的空间应确保可以存放机器基本字符集中任意字符对应的数字值。即一个char的大小和一个机器字节一样。

其它字符类型用于扩展字符集,如wchar_tchat16_tchar32_twchar_t类型用于确保可以存放机器最大扩展字符集中的任意一个字符;类型char16_tchar32_t则为Unicode服务(Unicode是用于表示所有自然语言中字符的标准)。

C++语言规定一个int至少和一个short一样大(大指的是最大位数),一个long至少和一个int一样大,一个long long至少和一个long一样大,其中,数据类型long long是在C++11中新定义的。

大多数计算机以2的整数次幂个bit作为块来处理内存,可寻址的最小内存块称为字节(byte)。存储的基本单元称为(word),通常由几个字节组成。在C++语言中,一个字节要至少能容纳机器基本字符集中的字符。大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4或8字节。

对于浮点数的类型和精度,有:

类型 字数(位数) 有效数字
float 1(32) 7
double 2(64) 16
long double 3或4(96或128) 硬件的实现不同,精度各不相同

字符型被分为了三种:charsigned charunsigned char。和int不同,类型char和类型signed char并不一样。字符型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。所以char实际上会表现为signed charunsigned char形式中的一种,具体是哪种由编译器决定。

unsigned char c=-1; // 假设char占8比特,c的值为255,因为unsigned char可以表示0到255共256个数字,所以最后的值是-1对256取模为255。

signed char c2=256; // 假设char占8比特,c2的值是未定义的,当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作,可能崩溃,也可能产生垃圾数据

intunsigned int相加的时候是int变成unsigned int的形式,然后再运算,即先进性类型转换。如果这里有int类型的-1和unsigned int类型的1相乘,结果本来希望是1,最后的结果为unsigned int的最大值。

for(unsigned u = 10; u >= 0; u--)会死循环,0之后是unsigned int的最大值。解决方法:

while(u > 0)u++;
while(u > 0) {
u--;
}

某些一望而知类型的常量叫做字面值常量,比如1.0、21、024、0x14。

整型字面值具体的数据类型由它的值和符号决定。默认情况下,十进制字面值是带符号数;八进制和十六进制字面值既可能是带符号的也可能是无符号的。十进制字面值的类型是intlonglong long中能容纳当前值中尺寸最小的那个;八进制和十六进制字面值的类型是能容纳其数值的intunsigned intlongunsigned longlong longunsigned long long中的尺寸最小者。但如果均放不下,则会产生错误。

十进制字面值不会是负数,比如-21是一个负十进制字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负值而已。

如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分割,则它们实际上是一个整体。当书写的字符串字面值比较长,写在同一行里不太合适时,就可以采取分开书写的方式。比如:

cout << "abc "
"def" << endl;

通过添加前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型:

  • L作为前缀可表示宽字符型字面值,类型为wchar_t,比如:L'a'
  • u/U作为前缀可表示Unicode 16/32字符型字面值,类型为char16_t/char32_t
  • u8作为前缀可表示UTF-8字符串型字面值,类型为char

浮点型字面值中fF作为后缀时表示的类型为floatlL作为后缀时,类型为long double


第二节 变量

对象是指一块能存储数据并具有某种类型的内存空间

初始化不是赋值的一种!!!在C++中,初始化和赋值是两个完全不同的操作。

列表初始化:定义一个名为aint变量并初始化到0,以下的4条语句都可以做到这一点:

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

这是c++11新标准的一部分,用花括号来初始化变量的形式被称为列表初始化,无论是初始化对象还是为对象赋新值,都可以使用这样一组由花括号括起来的初始值。

这几句的区别:

  • 初始化四句都可以,但是赋值操作时,a(0)a{0}不合法,只能是a = (0)a = {0}
  • ()不会检查类型的问题,可以强制类型转换,但是{}会判断类型是否正确,如果不正确直接报错。

为了允许把程序拆分成多个逻辑部分来编写,C++语言支持分离式编译(separate compilation)机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。这需要有文件间共享代码的方法,一个代码可能需要使用另一个文件中定义的变量。一个实际的例子是cincout,定义于标准库,却能被我们写的程序使用。

为了支持分离式编译,C++语言将声明和定义区分开来。声明(declaration)使得名字被程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)负责创建与名字关联的实体。即变量可以杯声明多次,但只能被定义一次。

声明变量但并不定义变量,使用extern,而且此时不能显式地初始化变量,因为显式初始化的声明会变成定义,即extern double pi = 3.1415926相当于让extern失效 。

C++是一种静态类型语言,其含义是在编译阶段检查类型。在C++语言中,编译器负责检查数据类型是否支持要执行的运算。

变量命名规范:

  • 变量名一般用小写字母
  • 用户自定义的类名一般以大写字母开头,比如Sales_item
  • 如果标识符由多个单词组成,则单词间应有明显区分,即用下划线分开或下一个单词首字母大写

现在有一个全局变量bmain函数中有一个局部变量b,那么还怎么访问那个全局的b呢?用::b,我们说过::是作用域操作符,因为全局作用域本身并没有名字,所以左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。


第三节 复合类型

我们平时说的引用指的是左值引用。

引用就是起了个别名。

引用必须被初始化,因为和它的初始值对象要一直绑定在一起,没有自己的内存,所以引用并非对象。

比如ba的引用,c也是引用,可以用int &c = a;,也可以用int &c = b;

10这种常量得用const int &,不能用int &。00

引用与实际对象的类型必须得一样。

指针和引用的不同点:

  • 指针本身也是一个对象,允许对指针赋值和拷贝。
  • 指针的生命周期里可以指向不同的对象,引用不能变。
  • 指针无需在定义时赋初值,如果没有被初始化,会有个不确定的值。

赋值空指针的方法:

int *p1 = nullptr;  // C++11新标准引入的特殊类型字面值,可以被转成任意其他的指针类型
int *p2 = 0;
int *p3 = NULL; // 需要先#include cstdlib

预处理器是运行于编译过程之前的一段程序,预处理变量不属于命名空间std,由预处理器负责管理,因此可以直接使用预处理变量而无需在前面加std::

新标准下最好使用nullptr,不尽量避免使用NULLNULL是老代码中用的,并且预处理程序直接把它变成0了。

指针是否相等看的是地址是否相等,而不是指的是不是一个东西,比如:

int a[3][4];
int *p1 = a[0];
int *p2 = &a[0][0];
cout << (p1 == p2); // 1

void *虽然可以接收任意类型的指针,但是操作有限,比如可以进行地址比较、作为函数的输入或输出、赋值给另一个void *,但是不能直接操作该对象,因为不知道类型,也就不知道可以怎么操作,在void *的视角来看,内存空间仅仅只是内存空间。

int* p1, p2可以这么写,但是会产生误导,因为这里的意思是p1int *p2int

指向指针的指针:

int ival = 1024;
int *pi = &ival;
int **ppi = &pi;
cout << **ppi << endl; // 1024

ppi存的是pi的地址,pi存的是ival的地址。

因为引用不是一个对象,所以不存在引用的指针;但是指针是对象,所以存在指针的引用:

int i = 24, *p = &i;
int *&r = p; // 注意次序,离变量越近的东西对变量的影响越大,所以是个引用,然后是指针的引用,最后是一个int类型指针的引用。
cout << *r << endl; // 24

拓展:

int i = 24, *p = &i;
int j = 42;
int *&r = p;
cout << *r << endl; // 24
p = &j;
cout << *r << endl; // 42(跟着p走)

和:

int i = 24, *p = &i;
int j = 42;
int* const &r = &i; // 这个是常量,所以要在*和&之间加个const,然后r和i的地址锁死
cout << *r << endl; // 24
p = &j;
cout << *r << endl; // 24,p动不影响r的值。

第四节 const限定符

const限定的变量不是说改变值会报错,是赋值操作就会报错,值和原值一样也会报错。注意:全局变量也不可以省略初始化而认为它是0,也会报错。

与非const限定变量相比,const能完成大部分操作,比如强制类型转换。

变量之间互相初始化,和它们是不是const无关,都不会报错,因为只传递值,一旦拷贝完成,二者便没有什么关系。

编译器在编译过程中把用到const变量的地方都替换成其对应的值。

默认情况下,const对象仅在文件内有效。我们如果希望只有一个该变量,剩下的文件都使用这一个const变量。解决方法是不管是声明还是定义都添加extern关键字,这样只需要定义一次就行了:

// a.cc文件中定义并初始化一个常量,该常量能被其他文件访问,需要用extern!!!
extern const int bufsize = func(); // a.h头文件中声明,和a.cc中的是同一个
extern const int bufsize;

常量的引用:const int &r1 = c1;,引用及其对应的对象都是常量。不能用非const类型引用指向const对象。因为不能通过引用来改变原const对象的值。反过来可以,因为改变int对象的值而使const引用的值改变无所谓,引用本身也不是对象。

Q:一个常量引用被绑在一个对象身上发生了什么呢?看下面的一串代码:

double dval = 3.14;
const int &ri = dval;
cout << ri << endl; // 3

为了确保const int引用绑定的是个const int,编译器搞了个临时变量temp出来:

const int temp = dval;  // temp = 3
const int &ri = temp; // ri绑定临时常量

对于const来说,指针的规则和引用一样,不能让普通的int指针指向const int常量。

int *const是常量指针(从右往左读),指的是从一而终都指向这一个东西。

我们定义让自己是常量的const叫做顶层const,让指向对象为常量的const为底层const

常量表达式:值不会改变且在编译过程中就能得到计算结果的表达式。

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

const int max_files = 20;  // max_files是常量表达式
const int limit = max_files + 1; // limit是常量表达式,因为max_files被20替代,所以直接可以知道是21
int staff_size = 27; // 不是const,所以不是常量表达式
const int sz = get_size(); // 因为不是编译过程能计算出来的,所以不是常量表达式

C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。对于constexpr int sz = size();,只有size是一个constexpr函数时,才是一条正确声明语句,而新标准允许定义一种特殊的constexpr函数,这种函数足够简单以使得编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量了。(见第六章)

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

算术类型、引用和指针都属于字面值类型,可以定义成constexpr类型。指针和引用虽然可以这样被定义,但是初始值受到严格限制,一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。

函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。(还真每次都不变)

但是constexpr指针指的是自己是常量,而不是所指对象为常量:constexpr int *q = nullptr; // q是一个指向整数的常量指针。即constexpr不管是不是指针都是顶层const

constexpr int i = 42;  // i是整型常量,和const的区别是会检查右侧是不是常量表达式
int j = 21;
constexpr const int *p = &i; // 常量指针指向常量
constexpr int *p1 = &j; // 可以指向非const

第五节 处理类型

复习:

typedef double wages;
typedef wages base, *p; // base = double, p = double*

还可以用using,即别名声明来定义类型的别名:using SI = Sales_item;,就可以用SI代替Sales_item类型了。

注意,不是说typedef就是直接替换的意思!!!:

typedef char *pstr;
const pstr cstr = 0; // 注意这里相当于:char *const ctr = 0; 即cstr为常量指针而非指针常量,直接换就错了
const pstr *ps; // char **const ps; 即*挪到前面去

C++11新标注引入了auto类型说明符,可以让编译器替我们分析表达式所属的类型。auto让编译器通过初始值来推断变量的类型,显然auto定义的变量必须有初始值。

可以理解为auto换成一个词,这个词是类型。所以有:

auto i = 0, *p = &i;  // 正确,i为int,p为int *。
auto sz = 0, pi = 3.14; // 错误,sz为int,pi为double。

auto初始化的右侧为引用的情况下,类型为本身变量对象的类型,而不是也是引用。

auto忽略掉顶层的const,而保留底层的const

const int ci = 5;
auto e = &ci; // e是一个指向整数常量的指针(因为对常量对象取址就是让这个所指对象是常量,所以是底层const,应该保留,所以e是const int*。而如果是普通的int,这里就是int*。

如果auto前还有const,这个const为顶层的(很显然因为顶层被忽略,所以这里是补的顶层的const)。

而如果要让auto带上引用的话,可以用auto &。比如const auto &j = 42;,但如果auto &j = 42;就错了,会识别为int &j,于是报错。但是:

const int ci = 10;
auto &g = ci; // 这个g就被识别为const int &,因为这个const同样是说被指向的对象是常量,所以为底层的const

希望从表达式的类型中推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。C++11decltype,它的作用是选择并返回操作数的数据类型,并不实际计算表达式的值。比如:decltype(f() + 1) sum = x;规定sum的类型,甚至不用调用f()

decltype处理顶层constauto不同,decltype不会忽略const也不会忽略引用。引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外

decltype(*p)的结果类型是int &

decltype中加括号是一种操作符,相当于取址,即可得到引用类型:decltype((i)) d;是错误的,因为为int &必须初始化。decltype((variable))一定是引用,而decltype(variable)只有当variable是引用的时候才是引用。

第六节 自定义数据结构

一般来说,最好不要把对象的定义和类的定义放在一起,一会定义类,一会定义变量,是一种不被建议的行为。

确保头文件多次包含仍能安全工作的常用技术是预处理器。之前涉及到的预处理功能为#include,当预处理器看到#include标记时会用指定的头文件的内容代替#include

C++程序还会用到的一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。#ifdef当且仅当变量已定义为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。

为了避免与程序中其他实体发生名字冲突,一般把预处理变量的名字全部大写。

C++ primer 5th 第二章 变量和基本类型 阅读笔记的更多相关文章

  1. C++ Primer 笔记(2)第二章 变量与基本类型

    第二章 变量与基本类型 1.基本内置类型包括算术类型和空类型,算术类型分为两类:整型(包括字符和布尔类型)和浮点型: 2.布尔类型(bool)的取值是真(true)或者假(false): 3.字面值常 ...

  2. [C++Primer] 第二章 变量和基本类型

    第二章 变量和基本类型 引用 引用定义的时候必须初始化. 引用初始化之后无法重新绑定到其它对象上. 引用本身并不是对象,所以没有指向引用的引用(不管如何多层引用,引用的还是源对象) 下面用一个简单的例 ...

  3. C++ Primer 第2章 变量和基本类型

    C++ Primer 第2章 变量和基本类型 C Primer 第2章 变量和基本类型 1 基本内置类型 算数类型 类型转换 字面值常量 2 变量 变量定义 3 复合类型 引用d左引用 指针d 4 c ...

  4. <<C++ Primer>> 第二章 变量和基本类型 术语表

    术语表 第 2 章 变量和基本类型 地址(address): 是一个数字,根据它可以找到内存中的一个字节    别名生命(alias declaration): 为另一种类型定义一个同义词:使用 &q ...

  5. 《C++ Primer》读书笔记—第二章 变量和基本类型

    声明: 文中内容收集整理自<C++ Primer 中文版 (第5版)>,版权归原书所有. 学习一门程序设计语言最好的方法就是练习编程. 1.8比特的char类型计算机表示的实际范围是-12 ...

  6. 《C++primer》v5 第2章 变量和基本类型 读书笔记 习题答案

    2.1 int,long long ,short 可表示范围和占用内存空间不同.具体与计算机有关. 无符号类型只能表示0和正数,带符号类型可以表示负数,0,正数. float是单精度,一般占用4个字节 ...

  7. C++ Primer 读书笔记 第2章 变量和基本类型

    C++ Primer 第二章 变量和基本类型 2.1 基本内置类型 C++定义了一组表示整数.浮点数.单个字符和布尔值的算术类型(arithmetic type),此外还定义了Void类型. 算术类型 ...

  8. 【C++】《C++ Primer 》第二章

    第二章 变量和基本类型 指针和引用的不同点 引用不是一个对象,它没有实际地址,但是指针是一个对象.允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象. 指针无须在定义时赋初值.

  9. 逆向基础 C++ Primer Plus 第二章 开始学习C++

    C++ Primer Plus 第二章 开始学习C++ 知识点梳理 本章从一个简单的C++例子出发,主要介绍了创建C++程序的步骤,以及其所包含的预处理器编译指令.函数头.编译指令.函数体.注释等组成 ...

  10. Java 第二章 变量

    第二章 变量 变量称为:是计算机语言中能储存计算机结果或能表示值抽象概念 .变量可以通过变量名访问 int money ; //变量 money=1000; //赋值 int money=1000: ...

随机推荐

  1. JZOJ 4496. 【GDSOI 2016】第一题 互补约数

    \(\text{Problem}\) 求 \[\sum_{i=1}^n \sum_{d|n} \gcd(d, \frac{i}{d}) \] 有 \(n \le 10^{11}\) \(\text{A ...

  2. Mybatis连接数据库

    从零开始Mybatis连接数据库 创建Maven文件 File-->new-->project-->maven,点击next 配置 在出现的pom.xml文件中<project ...

  3. key对象转换数组title

    before <!DOCTYPE HTML> <html> <head> <title>key对象转换数组title</title> < ...

  4. OpenLayers地图标注及弹窗实现

    1. 引言 地图标注是在地图中进行文字或图标的标注,从而显示对应的信息 本文基于OpenLayers实现地图上图文的标注与弹窗显示 OpenLayers官网:OpenLayers - Welcome ...

  5. linus->查看文件及文件夹大小相关命令

    背景: 经常会遇到服务器服务突然停了,去服务器一看服务正常运行. 然后在排查服务器容量,发现100%使用. 那么记下来一些常用命令是有必要的. 相关命令: df -hl   查看占用情况. du -s ...

  6. 苹果手机第一次fixed没有达到预期效果,滚动下页面就正常了

    我们用ul li实现了一个视频列表,一共两列,点击其中一个播放时,会将该li设置为position: fixed;width:90%;也就是变成了一个弹窗的样式.安卓手机一切正常,然而当看到苹果,我- ...

  7. 2373. 矩阵中的局部最大值 (Easy)

    问题描述 2373. 矩阵中的局部最大值 (Easy) 给你一个大小为 n x n 的整数矩阵 grid . 生成一个大小为 (n - 2) x (n - 2) 的整数矩阵 maxLocal ,并满足 ...

  8. c++练习272题:金币

    *272题 原题传送门:http://oj.tfls.net/p/272 题解:(遍历,60分) #include<bits/stdc++.h>using namespace std;lo ...

  9. 关于el-popover的箭头颜色

    因为会从四个方向不定弹出 所以需要写入4个方向: 注意弹出框是动态插入body中的,需写入根部样式中 如果有多个地方用到且颜色都不一样,可以用类名区分,elm提供了 然后把前面的 .el-popper ...

  10. PHP接口微信支付

    PHP后台调用微信支付下单function wx_getPayRequest($openid, $orderid, $rmb, $title,$appoids){ $nonce = $orderid. ...