C++ 记事本: 变量
C++ 变量也许和其他语言的变量没有什么差别.就是用来存储一些可能会变值的容器.
当然 C++ 变量里又分为 原子类型 的(int , char ,bool 等等),复合类型 的(struct ,class ,union).
这篇内容会比较侧重与于记录些原子类型的变量的使用和注意事项.其实我想很多都记录在原文中,但是无奈怕篇幅太长.而且让人生厌.所以给出了一些链接让有兴趣的同学方便查看.
-- 变量类型 --
我们可以按照变量的实际保存数据的类型分割为一下几个类别
- 整型
int
(4字节)
short
(2字节)
long
(在 32 位系统下是 4 字节,64 位系统则是 8 字节) - 实型
float
(4字节)
double
(8字节) - 字符型
char
(1字节)
wchar_t
(这个在内存中的所占的大小,可能根据使用的 字符集 不同而不同,比如你在 VS 上用默认是 utf16le 那么它大致是占 2 个字节,gcc 上默认是 utf8 ,其宽度是 4 字节)
[还有在使用 wchar_t , char 类型时候需要注意区分要使用与之对应的容器或函数.比如 char 对应的字符串容器是 string,而 wchar_t 对应的容器是 wstring,而在屏幕输出上 char 对应的 cout,而 wchar_t 对于的是用 wcout] - 布尔类型
bool
(1字节) - 指针类型
函数指针
(例如 int (*pFunc) (void),这里定义了一个 pFunc 函数指针,指向返回值为 int ,参数列表为 void 的函数指针.这个是属于 c 范畴的内容啦.所以这里不详细介绍了有兴趣的可以点这个 链接(函数指针) 或这个 链接( C++ 成员函数指针) 参考看看.我在一开始时候学习到这个特性的时候,还是很痛苦的.经常和返回值为指针的函数原型弄混)
指针变量
(用于记录某些变量地址)
-- 变量定义 --
这里简单的列举出来变量的定义形式(引用必须在声明时候显示赋值):
int i; // 定义整型变量
int *p; // p 为指向整型数据的指针变量
int a[n]; // 定义整型数组 a 有 n 个元素
int *p[n]; // 定义指针数组 p,有 n 个指向整型指针
int f(); // f 为返回整型函数值的函数
int *p(); // p 为返回一个指针的函数,该指针指向整型数据
int (*p)(); // p 为指向函数指针,该函数返回一个整型值
int **p; // p 为一个指针变量,它指向一个整型的指针变量
int &p = a; // p 为整形变量 a 的引用
int *& p2 = p; // p2 为整形变量指针 p 的引用(可以查看这里的例子)
那在实际的开发中在定义变量时候最好给变量显示赋予一个初始值.为什么这么做呢,若你在定义变量的时候并未显示赋值,那么你的变量一开始里面保存的数据是不确定的.比如定义一个整形 int a; 这句话仅仅只是为 a 这个变量分配了一个内存空间,而 a 的内容取决于你当前分配那块内存的值,那这个就是很不确定的了.所以假设你不小心使用了一个为经初始话的变量,可能就会导致程序崩溃等等.
-- 整形变量 --
整形变量就是用来存放整数的地方.那么整数又是如何被存储在内存当中的呢?答案是以 补码 (这里是有关 [原码] ,[反码] ,[补码] 的相关说明,若想知道的更深入可以 [点这里])的形式存放在内存中.若了解了这个的话,那么算术异常你也就大致知道是什么原因了,为何两个很大的数相加会得到一个负数.
还需要注意的是 C++ 对于 1000 0000 表示的数变不在是 0 而是 -128(即 -127-1).因为在有符号数中表示 -0 的这个数实际意义上是最小负数在减一. 当然实际是应为 -128 刚刚好对应的二进制补码为 1000 0000 除符号位外也.
在计算机当中其实它只识别二进制的形式,即 0101 这种的形式(因为没有什么电路的专业知识所以这里也不多做解释,以免误导到你.我的理解是电路中存在两种状态即,有电,没电.对应的即是 1, 0).
因为计算机的整形存储是以二进制的形式.所以有个细节可以注意,对于整数进行 2 的次幂乘除的时候,可以用移位来实现.这样效率会更高
下面来扩展下思路.既然计算机存储的数据格式是二进制,那么我们就简单下如何用布尔运算来实现有符号整数加法与乘法的方式.这里只是做个扩展思路,实际计算机的操作可能有被优化过.
-- 加法 --
一个加法可以拆分成布尔运算的 *与* 与 *异或* 的组合的多次迭代
两个数相与后再左移 1 位可以得到这两个数相加的 *进位*,
而两个数的异或可以得到两个书相加的 *本位*
若 *进位* 为 0 那么,当前的 *本位* 及是加法的运算结果,若不为 0 重复将 *进位* 与 *本位* 继续进行 *与* 与 *异或* 直至 *进位* 为 0.
举个例子吧
第一轮:
本位: 101001 ^ 001001 = 100000
进位: 101001 & 001001 = 001001 << 1 = 010010
第二轮:
本位: 100000 ^ 010010 = 110010
进位: 100000 & 010010 = 000000(为 0 结束)
所以的到 101001 与 001001 的相加结果 110010
-- 乘法 --
乘法的本质就是加法的迭代,那么我们可以用更贴近计算机处理的思路来想问题.若 n * m,可以讲 m * n 拆分成 m*(2^x + 2^y + 2^w + ...)
举个例子: 比如 6 * 5 , 然后我按照 m*(2^x + 2^y + 2^w + ...) 的拆分可以得到 6(2^2 + 2^0),那下面我就用二进制的表达式列出下运算公式.
110 * 101 = (110 << 000) + (110 << 010) = 110 + 11000 = 11110
这里看似正常,但是若 n,m 中出现了负数的话运算结果却会令人瞪目结舌.正像上面说的负数也是以补码形式存放在内存中的.那么我们可能要在运算前对 m,n 进行一步处理,即把 m,n 还原成源码再然后在进行运算.当然在运算时候切记不要把符号位带入运算当中.而且获得运算结果后记得把结果转换为补码的形式.
-- 浮点型变量 --
既然了解了整形变量是如何存储在内存当中的,那么我们也了解下浮点型是如何存储在内存中的吧.浮点型是遵照 [IEEE 754] 标准.也可以点 [这里] 简单地了解下什么是浮点数.也正因为它存储数据的方式.导致它所表达的具体值可能存在精度不够准确.
-- 字符型变量 --
既然计算机只能识别些 1010 的二进制那么字符又是如何被存储的呢?当然你不可能 'a' 这个字符直接写入内存中,那么就找个折中办法吧.我们用一个数值来对应一个字符.然后把这个记录在内存中,接着我们就可以通过这个数值找个这个字符.下面我们就简单的了解一些相关的专业名词和对于的解释吧.
- 字符(Character)是指人类语言中最小的表义符号.例如 'A'、'B' 等;想知道更多点 [这里]
- 字符编码(Encoding) 是指给定一系列字符,对应每个字符赋予一个特殊的数值,并可以用这数值来代表对应的字符.例如,我们给字符 'A' 赋予数值 0,给字符 'B' 赋予数值 1,则 0 就是字符 'A' 的编码;想知道更多点 [这里] 一帮我们使用比较多的编码集是 ASCII , Unicode(utf-8,utf-16), GBK.
- 字符集(Character Set) 是指给定一系列字符并赋予对应的编码后,所有这些字符和编码对组成的集合就.例如,给定字符列表为{'A','B'}时,{'A' => 0, 'B' => 1}就是一个字符集;
- 字符序(Collation) 是指在同一字符集内字符之间的比较规则;确定字符序后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系;每个字符序唯一对应一种字符集,但一个字符集可以对应多种字符序,其中有一个是默认字符序(Default Collation);
这里可能要补充一点 当你在使用 char 时候,若第一个字符大于 0x80,那么就必须检查下一个字节,才能判断一个完整的字符.
-- 变量作用域 --
在使用变量的时候应该注意变量的生存周期(即变量的作用域),以免引用了已经脱离作用域的变量,比如返回了一个指向函数局部变量的指针.可参看下表:
变量存储类别 函数内 函数外
作用域 存在性 作用域 存在性
auto 与 register √ √ × ×
static(局部) √ √ × √
static(外部) √ √ √(只限本文件) √
extern √ √ √ √
在初步了解变量的作用域,顺带这里也可以初略解下 C++ 在编译的程序时候会把程序内存分为的几个部分:
- 栈区: 由编译器自动分配释放的,存放着函数的参数值,局部变量的值,其操作方式类似于数据结构中的栈(申请)速度快,但程序员无法控制.
- 堆区: 由程序员分配释放,程序员不释放程序结束时可能回收,分配方式类似链表(申请速度慢,易产生内存碎片).
- 全局区/静态区: 全局变量与静态变量的存储是放一块的,初始化全局变量在一块区域内,未初始化的静态变量在相邻的另一块区域内,程序结束后由系统释放,内存在整个运行期间都存在.
- 字符常量区: 常量字符串就是放在这里,程序结束后由系统释放.
- 程序代码区: 存放函数体的二进制代码,在这里存放的变量又称字面常量