C++中变量类型及存储类型
C++变量的作用域有多种,综述:
(1)作用域为全局的变量在定义位置到文件结尾之间都可用
(2)自动变量的作用域为局部
(3)静态变量的作用域是全局还是局部取决于它是如何被调定义的
变量的定义是通过变量声明语句来实现的,变量声明语句的一般格式为:
[<存储类>]<类型名><变量名>[=<初值表达式>],...;
<存储类>有四种,它们分别是auto、register、static、extern。
<类型名>为已存在的一种数据类型名称,如char,short,int,long,float,double等基本数据类型名,或者用户定义的数据类型名。
<变量名>是用户定义的一个标识符,用来表示一个变量,该变量可以通过后面的可选项赋予一个值,称为给变量赋初值,也叫做对变量进行初始化。C+ +中标识符是区分大小写的,也就是说,大写字母和小写字母被认为是不同的字母。
变量名的命名遵循如下规则:
(1) 不能是C+ +关键字;
(2)第一个字符必须是字母或下划线;
(3)中间不能有空格;
(4)变量名中不能包括;,′″+-之类的特殊符号。
实际上变量名中除了能使用26个英文大小写字母和数字外,只能使用下划线“_”。
2变量的使用方式
(1)全局变量和局部变量
全局变量是在所有函数定义、类定义和程序块之外声明的变量。声明全局变量时如果在程序中不对它进行专门的初始化,该变量会被系统自动初始化为0。在程序的任何一个函数、类或程序块之内均可以访问全局变量。
局部变量是在某个函数定义、类定义或程序块之内声明的变量。局部变量只能在声明它的函数、类或程序块中被访问。
(2)生存期与作用域
生存期是指从一个变量被声明且分配了内存开始,直到该变量声明语句失效,它占用的内存空间被释放为止。一个全局变量的生存期从它被声明开始,直到程序结束;一个局部变量的生存期从它被声明开始,直到包含它的最近的一个程序块结束。
作用域是指变量名可以代表该变量存储空间的使用范围。
一般情况下,变量的作用域与其生存期一致,但由于C+ +语言允许在程序的不同部分为不同变量取同一名字,因此一个变量名的作用域可能小于其生存期。
(3)变量的存储类属性
在C+ +中变量还可以按存储分配方式的不同被划分为4种不同的存储类别,它们分别是:
①auto变量:用关键字auto声明的局部变量称为自动变量。auto为变量声明时的默认存储类别,即在变量定义时,如果不显式标明存储类别,则系统自动按auto变量处理。auto变量所占用存储空间的分配和释放工作将由系统自动完成。
②register变量:用关键字register声明的局部变量称为寄存器变量。register变量可能以寄存器作为其存储空间。声明寄存器变量时,关键字register的作用只能是建议(而不是强制)系统使用寄存器,原因是寄存器虽然存取速度快,但空间有限,当寄存器不够用时,该变量仍然按自动变量处理。
③static变量:用关键字static声明的变量称为静态变量。任何静态变量的生存期将延续到整个程序的终止。与全局变量一样,为静态变量分配的存储空间在整个程序运行过程中不再被释放;如果静态变量未被赋初值,系统将自动为其赋初值为0。
④extern变量:用关键字extern声明的变量称为外部变量。变量一旦被声明为外部变量,系统就不必像一般变量那样为其分配内存,因为该变量已在这一局部的外面被定义。外部变量一般用于多个文件组成的程序中,有些变量在多个文件中被声明,但却是指同一变量。标明某一变量为外部变量可以避免为其重复分配内存。
1.自动变量
a.函数中声明的函数参数和变量
b.代码块中定义的变量
C++编译器对自动变量的实现为,程序留出一段内存,并将其视为栈(由于新数据放在原数据的上面,且新数据会最先被销毁,类似栈),当程序使用完该自动变量时,会将其从栈中删除。当函数或者代码块运行结束的时候,自动变量将不再存在
2.静态变量
静态变量提供了3中链接性:
外部链接性(可在其他文件中访问):在代码块的外部声明
内部链接性(只能在当前文件中访问):在代码块的外部声明,且使用static限定符
无链接性(只能在当前函数或代码块中访问):在代码块内声明,且使用static限定符
不论哪种链接性变量,在整个程序的执行过程中,会一直存在,与自动变量相比,它们的寿命更长。如果未进行初始化,编译器将其初始化为0;
自动变量和静态变量最大的区别在于:编译器对两者的处理不一样,对自动变量,采用栈;对静态变量,编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序的执行期间一直存在。
C++提供两种变量的声明:
一种是定义声明,即定义;它给变量分配存储空间,可进行初始化,有两种方式
Int a;
Extern int a = 1;//进行初始化
另一种是引用声明,它不给变量分配存储空间
如果在多个文件中使用外部变量(全局,且具有外部链接性),只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它
(4)typedef类型说明
使用关键字typedef可以为已有类型名定义一个新类型名。其语法格式为:
typedef<已有类型名><新类型名>
typedef类型说明并没有真正地定义新的数据类型,它只是相当于给某个已有的数据类型起了一个别名。在规模较大的程序中为了提高代码可读性常采用这种形式。
3符号常量声明语句
符号常量在使用之前必须先进行声明。符号常量声明语句同变量声明语句类似,其语法格式为:
const<类型名><符号常量名><初值表达式>……;
其中,关键字const指明这是一条符号常量声明语句,后面跟着符号常量的类型名,接着是符号常量名,它是一个用户定义的标识符,符号常量名之后为一个赋值号和一个初值表达式。由此可见,必须在声明符号常量的同时为其赋初值。该语句也可以声明多个符号常量。
系统执行符号常量声明语句时,需要依次为每个符号常量分配存储单元并赋初值。一个符号常量被声明后,它的值就是声明所赋予的初值,作为常量,这个值以后将始终保持不变,因为系统只允许读取它的值,而不允许再次向它赋值。另外,在符号常量声明语句中,若<类型名>为int,则int可省略。
符号常量声明语句既可以出现在函数体外,也可以出现在函数体内,这一点也跟变量定义语句相同。
C+ +关键字中的true和false就是系统预先定义的两个符号常量,它们的值分别为1和0。使用符号常量往往可以提高程序的可读性和可维护性。由于符号常量和变量同样要求系统为其分配内存单元,所以可以把符号变量视为一种不允许赋值改变的或只读不写的变量,称其为const变量。
4使用#define命令定义符号常量
# define命令是一条预处理命令,也可以用它来定义符号常量。其命令格式为:
#define<符号常量名><字符序列>
<符号常量名>是用户定义的标识符,又称为宏或宏标识符;<字符序列>也是由用户给定的用来代替宏的一串字符序列,也称为宏替换体,它可以是数值常量、可计算值的表达式或字符串。宏被该命令定义后就可以使用在其后的程序中。当程序被编译时将把所有地方使用的宏标识符替换为对应的字符序列,并把宏命令删除掉。
其中AUTO关键字:
初始化变量的类型推断
初始化变量时,auto可以使用关键字代替,type以告诉编译器从初始化程序的类型推断出变量的类型。这称为类型推断(有时也称为类型推断)。
例如:
auto d{ 5.0 }; // 5.0 is a double literal, so d will be type double auto i{ 1 + 2 }; // 1 + 2 evaluates to an int, so i will be type int |
这也适用于函数的返回值:
int add(int x, int y) { return x + y; }
int main() { auto sum { add(5, 6) }; // add() returns an int, so sum's type will be deduced to int return 0; } |
虽然auto代替基本数据类型使用仅节省了一些(如果有的话)击键,但在以后的课程中,我们将看到一些示例,其中类型变得复杂而冗长。在这种情况下,使用auto可以节省很多打字时间。
函数的类型推断
在C ++ 14中,对auto关键字进行了扩展,以便能够从函数体内的return语句推断出函数的返回类型。考虑以下程序:
auto add(int x, int y) { return x + y; } |
由于x + y计算为int,编译器将推断此函数的返回类型为int。使用auto返回类型时,所有return语句必须返回相同的类型,否则将导致错误。
尽管这看起来很简洁,但我们建议对于常规功能避免使用此语法。函数的返回类型在帮助调用者记录期望函数返回的内容方面非常有用。如果未指定特定类型,则调用者可能会误解该函数将返回的类型,这可能会导致意外错误。
最佳实践
避免对函数返回类型使用类型推断。
感兴趣的读者可能想知道为什么auto在初始化变量时可以使用,但不建议用于函数返回类型。一个好的经验法则是auto可以在定义变量时使用,因为该变量从其推断类型的对象在语句的右侧可见。但是,对于函数而言并非如此-没有上下文可以帮助指示函数返回的类型。用户实际上必须深入研究函数主体本身,以确定函数返回的类型。它不那么直观,因此更容易出错。
尾随返回类型语法
的auto关键字,也可用于使用声明函数后返回的语法,其中,所述函数原型的其余部分之后指定的返回类型。
考虑以下功能:
int add(int x, int y) { return (x + y); } |
使用auto,可以等效地写为:
auto add(int x, int y) -> int { return (x + y); } |
在这种情况下,auto不执行类型推断-使用尾随返回类型只是语法的一部分。
您为什么要使用它?
一件好事是,它使您所有的函数名称都对齐:
auto add(int x, int y) -> int; auto divide(double x, double y) -> double; auto printSomething() -> void; auto generateSubstring(const std::string &s, int start, int len) -> std::string; |
目前,我们建议继续使用传统的函数返回语法。但是在第7.15节-lambdas简介(匿名函数)中,我们将再次看到这种尾随的返回类型语法。
功能参数类型的类型推断
许多新的程序员尝试这样的事情:
#include <iostream>
void addAndPrint(auto x, auto y) // only valid starting in C++20 { std::cout << x + y; }
int main() { addAndPrint(2, 3); // int addAndPrint(4.5, 6.7); // double } |
在C ++ 20之前,这是行不通的,因为编译器在编译时无法推断函数参数x和y的类型。在C ++ 20之前的版本中,如果要创建可用于各种不同类型的通用函数,则应使用function templates(在下一章中介绍),而不是type inference。
从C ++ 20开始,auto可以将关键字用作创建的简捷方法function templates,因此上述代码将编译并运行。请注意,这种使用auto不会执行类型推断。
对于高级读者
Lambda expressionsauto自C ++ 14起已支持参数。我们将在以后的课程中介绍lambda表达式。