C++深入体验之旅二:变量和数据

来源:互联网 发布:埃及夏朝 知乎 编辑:程序博客网 时间:2024/05/16 01:23

1.C++变量简介

1、什么是变量?

电脑具有存储的功能。我们可以通过Word打开一个保存的文章,也可以通过FPE(整人专家,一款游戏修改软件)来查看或锁定内存中保存的游戏人物的生命值。那么,一个程序是如何把数据存到电脑里,又是如何把电脑里的数据取出来的呢?
在设计程序的时候,我们把要存储的数据放在一个叫变量(Variable)的东西里。它就好像是一个箱子,而数据就是箱子里的物品。当然,在我们放东西和取东西之前必须要创建这么一个箱子,这条创建变量的语句又称为变量的声明(Declaration)。它的语句格式为:
[,变量名变量数据类型 变量名12,……变量名n];

2、变量的分类

  1. 根据作用域:可分为全局变量和局部变量。
  2. 根据生存周期:可分为静态存储方式和动态存储方式,具体地又分为自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。

静态存储方式是指在程序运行期间分配固定的存储空间的方式,动态存储方式是在程序运行期间根据需要进行动态的分配存储空间的方式。

3、变量的作用域和存储类别的关系

每一个变量均有作用域和存储类别两个属性,这些属性共同用于描述一个变量,这些不同类型的变量与存储位置的关系如下:
  1. 外部变量(全局变量)、静态外部变量、静态局部变量存储在静态存储区。
  2. 自动局部变量(局部变量默认为自动局部变量)、函数形参存储在动态存储区(即栈区)。
  3. 不论是静态存储区还是动态存储区均属于内存中的用户区。
  4. 而寄存器变量是存储在CPU寄存器中的而不是内存中。

4、与作用域相关的几个属性:

  1. 局部变量:在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的,这种类型的变量就称为“局部变量”。
  2. 全局变量:在函数外定义的变量,可以为本源文件中其它函数所公用,有效范围为从定义变量的位置开始到本源文件结束,这种类型的变量就称为“全局变量”。

5、与存储类型相关的几个属性:

  1. atuo:在声明局部变量时,若不指定 static,默认均是 auto,这类变量都是动态分配存储空间的,数据存储在动态存储区中。
  2. static:在声明局部变量时,使用关键字 static 将局部变量指定为“静态局部变量”,这样在函数调用结束后不消失而保留原值,即占用的存储单元不释放,在下一次函数调用时,该变量已有值就是上次函数调用结束时的值。
  3. register:在声明动态局部变量或者函数形参时,可将变量声明为register,这样编译系统就会为变量分配一个寄存器而不是内存空间,通过这种方式可提升对某些局部变量频繁调用的程序的性能。(寄存器运算速度远高于内存)
  4. extern:用于扩展全局变量的作用域。
    ①比如如果函数想引用一个外部变量,但该外部变量在该函数后定义,那么这个函数需要使用 extern 来声明变量,这样才能使用在该函数后面定义的全局变量。
    ②此外,extern 还可以在多文件的程序中声明外部变量。

2.变量的数据类型

我们刚才说了,变量就好像是一个箱子。可是不同的东西也要放到对应的箱子里。如果把吃的东西放在文具盒里,把衣服放进饼干盒里,显然是不合适的。变量也是一样的。有些数据是文字(字符或字符串),有些数据是数字(整数或者实数),把它们随便乱放,那么电脑就可能无法理解这些数据的含义了。
我们常用的基本数据类型由下表列出:(以VC为例)

在程序2.3中,char a;就是声明了一个字符型变量。修饰符是放在分类型之前的,比如要创建一个短整型变量A,就应该是short int A;了。
要注意,两个数据类型截然不同的变量是不能放在同一个语句中定义的。比如企图通过int a,char b;声明整型变量a和字符型变量b是不可以的。
我们在选择数据类型时,要尽量选择满足使用要求的类型。比如我们要算一个一元二次方程的解,就应该选择精度较高的浮点型或者双精度型,而不能选一个整数型;同时,我们也要有“够用就行”的好习惯。如果我们创建一个双精度型的变量去存储从整数1到100的和,那就显得大材小用,太浪费了。一个变量所占内存(Memory)的空间是和这个变量的数据类型有关的。虽然现在电脑的内存已经可以达到1GB,但是如果在设计大型软件时经常“大材小用”,即使有了更多的内存,也会捉襟见肘的。

3.变量名及变量命名规范

我们创建了一个箱子,用它的时候总应该用一样东西来表示它,那就是变量名。变量名的意义就如同给文件夹起个名字,或者给文件起个名字。不过,其名字也是要讲规则的。具体规则如下:

  1. 不能是可能与C++中语句混淆的单词。(这种单词称为保留字,具体哪些是C++的保留字可以查阅书后的附录。凡是在输入代码时,自动变成蓝色的单词,一定是保留字。)如果我们创建一个名为int的变量,那么这个int到底是一个变量名还是另一个变量的数据类型呢?电脑糊涂了。
  2. 第一个字符必须是字母或者是下划线。
  3. 大小写不同的变量名表示两个不相同的变量。C++是大小写敏感的。所以如果把C++中的语句打成大写字母,就会造成错误。
  4. 变量名中不应包括除字母、数字和下划线以外的字符。因为某些特殊字符在C++中具有分隔功能,电脑无法判断到底这是一个变量还是多个变量。
  5. 变量名应该尽量符合变量里面存放东西的特征。这样,自己和别人在阅读代码的时候才能一目了然。我们介绍两种比较常用的变量名标记法:驼峰标记法和匈牙利标记法。驼峰标记法是以小写字母开头,下一个单词用大写字母开头,比如numOfStudent、typeOfBook等等,这些大写字母看起来像驼峰,因此得名。而匈牙利标记法是在变量名首添加一些字符来表示该变量的数据类型,比如iNumOfStudent是表示学生数的整型变量,fResult是表示结果的浮点型变量等等。不过,如果一个程序实在是非常简单,那么用诸如a,b,c作为变量名也未尝不可,只要你能够记住这些变量分别应该存放什么数据就行了。

下面C++编程时通用的变量命名规范,其中也可能有一些个人偏好个人的偏向:
①属性部分:

全局变量 g_  常量 c_  c++类成员变量 m_  静态变量 s_ 
②类型部分:
指针 p  函数 fn  无效 v  句柄 h  长整型 l  布尔 b  浮点型(有时也指文件) f  双字 dw  字符串 sz  短整型 n  双精度浮点 d  计数 c(通常用cnt)  字符 ch(通常用c)  整型 i(通常用n)  字节 by  字 w  实型 r  无符号 u 
③描述部分:
最大 Max  最小 Min  初始化 Init  临时变量 T(或Temp)  源对象 Src  目的对象 Dest 
这里顺便写几个例子:
①hwnd : h 是类型描述,表示句柄, wnd 是变量对象描述,表示窗口,所以 hwnd 表示窗口句柄; pfnEatApple : pfn 是类型描述,表示指向函数的指针, EatApple 是变量对象描述,所以它表示 指向 EatApple 函数的函数指针变量。
②g_cch : g_ 是属性描述,表示全局变量,c 和 ch 分别是计数类型和字符类型,一起表示变量类 型,这里忽略了对象描述,所以它表示一个对字符进行计数的全局变量。

4.变量的初始化

前面我们说到,变量是存放在内存里面的。而内存又是有限的,在某些情况下,我们创建一个变量的时候,并不是真的重新造了一个“箱子”,而是把“弃置不用的旧箱子”拿来用。但是那些“旧箱子”里往往是有一些原来的数据,这些数据是不确定的。所以,在我们使用“箱子”之前,需要把原来的旧数据处理掉。这个过程称为变量的初始化(Initialization)。具体格式为:
    变量名=初始值;
或者我们可以在声明变量时,在变量名的后面加上“=初始值”即可,如int a=5;。在初始化的时候,要注意设置的初始值要符合变量的数据类型。

试试看:
1、试自己编一段程序,分别按整数类和浮点类输出由键盘输入的数值。
2、如果在主函数中声明的变量未经初始化,那么里面的数据是什么?
结论:未知的随机数值

5.常用的基本数据类型

整型(Integer)

所谓整型,就是我们平时说的整数。在VC++中,int默认为long int,即有符号的长整型数据。一个有符号的长整型变量在内存中占用4个字节的空间,它的表示范围是从-2147483648~2147483647。虽然整型数据无法表示小数,表示的范围也不是非常大,但是在它范围内的运算却是绝对精确的,不会发生四舍五入等情况。
我们常用整型数据来表示人数、天数等可数的事物。对于长度不是很长的编号,如学号、职工号,也可以用整型数据来表示。

实型(Real)

所谓实型,就是我们平时说的实数。在C++中,实数分为浮点型和双精度型。两者的主要区别是表示范围不同和占用的存储空间不同。我们可以用两种方式来表示实数:
1、小数形式,比如0.1、0.01、12.34等等,和我们平时日常生活中的表达并无异样。
2、指数形式,即科学记数法。比如0.25E5表示0.25×105,E表示10的多少次方,也可以用e表示。注意,在E之后的指数必须是整数。
虽然实数表示范围比整数要大得多,精度也更高,但是用它进行运算却不是绝对精确的。比如同样是800000000+1,整数的运算结果是800000001,而实数的运算结果却是8e+008。
我们常用实型数据来进行科学运算,比如计算一个数的平方根,或是纪录地球到月亮的距离。而温度、价格、平均数等可能出现小数的数据,我们也通常也用实型数据来表示。 

字符型(Character)

一个字符型变量可以存放一个半角西文字符或者一个转义字符。同时在字符两端要加上单引号,比如:char a='a',b='\n';。
要注意,字符型数据'1'和整型数据1是不同的。虽然它们输出时的现象是一样的,但是它们存储的内容是不一样的。关于字符型数据的存储,我们将在后续章节中再作介绍。

布尔型(Boolean)

布尔型数据的取值只能是0或1,也可以分别写作false和true。0表示假(false),1表示真(true)。但是,在我们以后学到的内容中,只要数值不等于0的都表示为真(包括负数)。所以,方便地记就称为“有真无假”。

6.C++常量

上一节我们介绍了变量,它是一种存储在电脑内存里,在程序中可以改变的数据。然而,有时候我们还会遇到一些数据,它们在程序中不应该被改变。比如圆周率π就应该等于3.1415926……,一年就应该是12个月,在程序中不应改变成其它的值。或者说,如果这些值无意之中被改变,会导致整个程序发生错误。这个时候,我们就需要一个不允许改变的“箱子”,我们称它为常量(Constant)。 
常量可以分为两种,一种是文字常量,也叫值常量,比如整数1,字符’a’就是文字常量;另外一种是需要通过自己定义的常量,它的表达和变量有些类似。
定义一个常量的语法格式为:
    const 常量数据类型常量名=文字常量;
我们可以认为,定义一个常量与定义一个变量的区别是在语句之前加上了const。但是,定义常量的时候必须对其进行初始化,并且在除定义语句以外的任何地方不允许再对该常量赋值。
特别地,如果一个实型文字常量没有做任何说明,那么默认为双精度型数据。若要表示浮点型数据,要在实数之后加上F;若要表示长双精度型数据,则要在实数之后加上L。
在cout语句中,我们说它可以输出字符串,这些带着双引号的字符串的全称是字符串常量,它也是一种文字常量。而带着单引号的常量称为字符常量,它与字符串常量是不同的,字符常量只能是一个字符,而字符串常量则可以是一个字符,也可以由若干个字符组成。
事实上,只要在不人为地改变变量值的情况下,常量可以由一个变量来代替。但是从程序的安全和严谨角度考虑,我们不推荐这样做。区别常量和变量的使用是一个优秀程序员需要具有的好习惯。

7.算术表达式

我们已经了解了程序设计中,最常用的两种存储方式——常量和变量。本节我们要学习如何在程序中运用常量和变量。我们先来看一段程序:(程序3.3)

#include "iostream.h" int main() {    float r;//创建一个浮点型变量存放半径    float l;//创建一个浮点型变量存放运算得出的周长结果    const float pi=3.1415926F;//定义常量pi等于3.1415926,最后的F表示这个数是浮点型    cout <<"请输入半径:";    cin >>r;    l=2*pi*r;//计算周长    cout <<"这个圆的周长为" <<l <<endl;    return 0; } 
程序的运行结果:
请输入半径:3 这个圆的周长为18.8496 
我们需要重点研究的是l=2*pi*r这句语句。这句语句称为赋值语句,赋值语句的语法格式为:
    左值=表达式;
语句中,等号称为赋值操作符。赋值操作符的作用就是把表达式的结果传递给左值。具体的过程是先将右侧的表达式的值求出,然后再将它存放到左值中。所以在赋值操作符两边出现相同的变量也是允许的。比如a=a+1就是先把原来a的值和1相加,然后再把结果放回变量a中。左值(Left Value,也作L-Value)的原意是在赋值操作符左边的表达式,它具有存储空间(比如自定义常量或变量),并且要允许存储(自定义常量只能在定义时初始化)。现在了解的知识中,左值可以理解为变量或定义语句中的自定义常量。
像程序中的2*pi*r我们称为算术表达式。它和平时数学上的表达式没有什么不同。如同四则运算一样,算术表达式中使用的是加减乘除和括号,运算的次序也是遵循“括号最先,先乘除后加减”的原则。需要注意的是:
表达式中,乘号是不能够省略的,即2a、4b之类的表达式是无法被识别的。
算术表达式中,括号只有小括号()一种,并且可以有多重括号。中括号[]和大括号{}都是不允许使用在算术表达式中的。比如((a+b)*4)是正确的写法,[(a+b)*4]却是错误的写法。
除、整除和取余
在C++中,“/”有两种含义:当除号两边的数均为整数时为整除,即商的小数部分被截去(不是四舍五入);除号两边只要有一个是实型数据,那么就做除法,小数部分予以保留,运算结果应当存放在实型变量中。
取余数的操作符为%,例如7%3的结果是1。它和乘除法类似,在加减法之前执行运算。注意,在取余数操作符的两边都应该是整数,否则将无法通过编译。
至此,我们已经学会了输入、输出和简单的运算。运用这些知识,我们已经能够自己设计一些简单的程序,实现一些计算功能。

试试看:
1、如果定义一个浮点型的常量时,不在实数之后加上F,是否能够通过编译?
2、假设已定义两个未初始化整型变量a和b,赋值语句a=b=1是否是合法的?如果合法,那么a和b的结果分别是什么?
3、7整除-2的结果应该是多少?-7%2的结果应该是多少?请上机验证。

8.全局、局部和静态局部变量

我们已经在前面学习了变量,并且能够熟练地使用它。可是,仅仅靠这些知识,有些问题仍然无法得到解决。

标志符

首先要来介绍一下什么是标志符。在程序设计的过程中,经常要给变量、函数甚至是一些数据类型起名字(还包括以后的类名,对象名等)。我们把这些用户根据一些规定,自己定义的各种名字统称为标志符(Identifier)。显然,标志符不允许和任何保留字相同。

全局变量和局部变量

在函数这一章节中,我们说过函数体内声明的变量仅在该函数体内有效,别的函数是无法使用的。并且在函数运行结束后,这些变量也将消失了。我们把这些在函数体内声明的变量称为局部变量(Local Variable)
然而,可能会遇到这样的问题:我们想要创建一个变量作为数据缓冲区(Buffer),分别供数据生成、数据处理和数据输出三个函数使用,三个函数都要能够读取或修改这个变量的值。显然通过传递参数或返回值来解决这个问题是非常麻烦的。
那么,我们能否建立一个变量能够让这三个函数共同使用呢?在C++中,我们可以在函数体外声明一个变量,它称为全局变量(global variable)。所谓全局,是指对于所有函数都能够使用。当然,在该变量声明之前出现的函数是不知道该变量的存在的,于是也就无法使用它了。另外,如果我们声明了一个全局变量之后没有对它进行初始化操作,则编译器会自动将它的值初始化为0。
下面,我们就用全局变量来实现刚才提出的那个问题:(程序11.1.1)

#include "iostream.h" #include "stdlib.h"//用于产生随机数,不必理会 #include "time.h"//用于产生随机数,不必理会 #include "iomanip.h"//用于设置域宽 void makenum(); void output(); void cal(); int main() {    srand(time(NULL));//用于产生随机数,不必理会    for (int i=0;i<4;i++)    {       makenum();//产生随机数放入缓冲区      cal();//对缓冲区的数进行处理       output();//输出缓冲区的数值    }    return 0; } int buffer;//定义全局变量,以下函数都能使用它 void makenum() {    cout <<"Running make number..." <<endl;    buffer=rand();//把产生的随机数放入缓冲区 } void cal() {    cout <<"Running calculate..." <<endl;    buffer=buffer%100; } void output() {    cout <<"Running output..." <<endl;    cout <<setw(2) <<buffer <<endl; } 

运行结果:
Running make number... Running calculate... Running output... 48 Running make number... Running calculate... Running output... 47 Running make number... Running calculate... Running output... 24 Running make number... Running calculate... Running output... 90 

以上为某次运行得到的随机结果。可见,使用全局变量使得多个函数之间可以共享一个数据,同时从理论上实现了函数之间的通讯。

静态局部变量

全局变量实现了函数之间共享数据,也使得变量不再会因为某个函数的结束而消亡。但是,新问题又出现了:一个密码检测函数根据调用(用户输错密码)的次数来限制他进入系统。如果把调用次数存放在一个局部变量里,显然是不可行的。虽然全局变量可以记录一个函数的运行次数,但是这个变量是被所有函数共享的,每个函数都能修改它,实在很危险。我们现在需要的是一个函数运行结束后不会消失的,并且其他函数无法访问的变量。
C++中,我们可以在函数体内声明一个静态局部变量(Static Local Variable)。它在函数运行结束后不会消失,并且只有声明它的函数中能够使用它。声明一个静态局部变量的方法是在声明局部变量前加上static,例如:
    static int a;
和全局变量类似,如果我们没有对一个静态局部变量做初始化,则编译器会自动将它初始化为0。
下面,我们就用静态局部变量来模拟一下这个密码检测函数的功能:(程序11.1.2)
#include "iostream.h" #include "stdlib.h" bool password();//密码检测函数 int main() {    do    {    }    while (password()!=true);//反复检测密码直到密码正确    cout <<"欢迎您进入系统!" <<endl;    return 0; } bool password() {    static numOfRun=0;//声明静态局部变量存放函数调用次数    if (numOfRun<3)    {       int psw;       cout <<"第" <<++numOfRun <<"次输入密码" <<endl;       cin >>psw;       if (psw==123456)       {          return true;       }        else       {          cout <<"密码错误!" <<endl;          return false;       }    }    else    {       cout <<"您已经输错密码三次!异常退出!" <<endl;       exit(0);//退出程序运行    } } 

第一次运行结果:
第1次输入密码 111111 密码错误! 第2次输入密码 222222 密码错误! 第3次输入密码 0 密码错误! 您已经输错密码三次!异常退出! 

第二次运行结果:
第1次输入密码 000000 密码错误! 第2次输入密码 123456 欢迎您进入系统! 

使用静态局部变量可以让函数产生的数据更长期更安全地存储。如果一个函数运行和它以前的运行结果有关,那么一般我们就会使用静态局部变量。

9.变量的作用域

在程序的不同位置,可能会声明各种不同类型(这里指静态或非静态)的变量。然而,声明的位置不同、类型不同导致每个变量在程序中可以被使用的范围不同。我们把变量在程序中可以使用的有效范围称为变量的作用域
任何变量都必须在声明之后才能被使用,所以一切变量的作用域都始于变量的声明之处。那么,它到什么地方终止呢?我们知道C++的程序是一个嵌套的层次结构,即语句块里面还能有语句块。最终语句块由各条语句组成,而语句就是程序中的最内层,是组成程序的一个最小语法单位。在某一层次声明的变量的作用域就终止于该变量所在层次的末尾。
举个例子来说明:

#include "iostream.h"  int main() {    int a=3,b=4;//变量a和b的作用域开始    for (int i=0;i<5;i++)//在for语句内声明的变量i的作用域开始    {       int result=i;//变量result的作用域开始       if (int j=3)//在if语句内声明的变量j的作用域开始       {          int temp=8;//变量temp的作用域开始          result=temp+(a++)-(b--);       }//变量temp的作用域结束       else          result=2;//if……else语句结束,变量j的作用域结束       cout <<result <<endl;    }//for语句结束,变量i和result的作用域结束    return 0; }//变量a和b的作用域结束 

根据上面这段程序,我们发现每当一个语句块或语句结束,那么在该语句块或语句层次内声明变量的作用域也就结束了。所以,下面的这段程序就存在错误:
#include "iostream.h" int main() {    int a=3,b=4;    for (int i=0;i<5;i++)    {       int result=i;       if (int j=3)       {          int temp=8;          result=temp+(a++)-(b--);       }       else          result=2;       cout <<j <<result <<endl;//j的作用域结束,变量未定义    }    cout <<result <<endl; //result的作用域结束,变量未定义    cout <<i <<endl;//这里居然是正确的,为什么呢?    return 0; } 

变量j和result无法输出是在意料之中的,但是为什么明明变量i的作用域已经结束了,却还是能够正常输出呢?这是微软给我们开的一个玩笑。根据ANSI C++的标准,在for语句头中声明的变量的作用域的确应该在for语句的末尾结束。然而VC++却没有完全符合这个标准,它认为for语句头中声明的变量作用域到包含该for语句的最小语句块结束。尽管如此,我们还是应该按照ANSI C++标准来认知变量的作用域。

10.变量的可见性

我们之前介绍过,在某一个函数中,不应该有两个名字相同的变量。可是,我们拿下面这段程序代码(程序11.1.3)去测试一下,发现居然在同一个函数中可以有两个名字相同的变量。这又是怎么回事呢?编译器又是如何辨别这两个名字相同的变量的呢?

#include "iostream.h" int main() {    int a=3,b=4;    {       int a=5,b=6;       {          char a='a',b='b';          cout <<a <<b <<endl;       }       cout <<a <<b <<endl;    }    cout <<a <<b <<endl;    return 0; } 

运行结果:
ab 56 34 

我们已经说明,变量可以使用的范围是由变量的作用域决定。不同层次的变量的作用域,就好像大小不一的纸片。把它们堆叠起来,就会发生部分纸片被遮盖的情况。我们把这种变量作用域的遮盖情况称为变量的可见性(Visibility)。如下面的图11.1所示:

编译器正是根据变量的可见性,来判断我们到底引用哪个变量的。具体在程序中就是: 

#include "iostream.h" int main() {    int a=3,b=4;//整型变量a=3、b=4的作用域开始    {       int a=5,b=6;//整型变量a=5、b=6的作用域开始,整型变量a=3、b=4不可见       {          char a='a',b='b';//字符型变量a='a'、b='b'作用域开始,整型变量a、b不可见          cout <<a <<b <<endl;//输出字符型变量,整型变量a、b不可见       }//字符型变量a='a'、b='b'作用域结束       cout <<a <<b <<endl;//输出整型变量a=5、b=6,整型变量a=3、b=4不可见    }//整型变量a=5、b=6的作用域结束    cout <<a <<b <<endl; //输出整型变量a=3、b=4    return 0; }//整型变量a=3、b=4的作用域结束 

然而,当两张纸处于同一个层次,显然两者就不可能发生遮盖了。所以,如果我们在同一个层次中声明两个名字相同的变量,那么他们的作用域就不是遮盖,而是冲突了。
因此,在某个函数的同一语法层次内不能声明多个名字相同的变量。




原创粉丝点击