【C++基础】第八课:变量

变量定义,初始化,变量声明,标识符,作用域

Posted by x-jeff on May 20, 2019

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.变量定义

C++中每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。

在C++中,“变量(variable)” 和“对象(object)”一般可以互换使用。

🚩变量定义的基本形式:首先是类型说明符,随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。例如:

1
2
3
4
int sum=0,value,
      units_sold=0;//sum,value,units_sold的类型都为int,并且sum和units_sold的初始值为0
Sales_item item;//item是一个类对象,其类型为Sales_item
std::string book("0-201-78345-X");//book通过一个string字面值初始化

2.初始化

当对象在创建时获得了一个特定的值,我们说这个对象被初始化了。

在同一条定义语句中,可以用先定义的变量去初始化后定义的其他变量。

例如:

1
double price=109.99,discount=price*0.16;

2.1.初始化和赋值

⚠️初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

2.2.初始化方式

初始化有几种不同的形式,以下4种方式均可对变量进行初始化:

1
2
3
4
int units_sold=0;
int units_sold={0};
int units_sold{0};
int units_sold(0);

在【C++11标准】中,用花括号来初始化变量得到了全面应用,这种初始化的形式被称为列表初始化

❗️当用于内置类型(⚠️前提)的变量时,这种初始化形式有一个重要特点:如果使用列表初始化且初始值存在丢失信息的风险,则编译器将报错:

1
2
3
long double ld=3.1415926536;
int a{ld},b={ld};//报错:因为存在丢失信息的风险
int c(ld),d=ld;//转换正常进行,c=d=3,丢失了部分信息

2.3.默认初始化

如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了“默认值”。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。

如果是内置类型的变量(⚠️前提)未被显式初始化,它的值由定义的位置决定:

  • 定义于任何函数体之外的变量被初始化为0。
  • 定义在函数体内部的内置类型变量将不被初始化。一个未被初始化的内置类型变量的值是未定义,如果试图拷贝或以其他形式访问此类值将引发错误。

👉每个类各自决定其初始化对象的方式。而且,是否允许不经初始化就定义对象也由类自己决定。如果类允许这种行为,它将决定对象的初始值到底是什么。

绝大多数类都支持无须显式初始化而定义对象,这样的类提供了一个合适的默认值。例如,string类规定如果没有指定初值则生成一个空串:

1
std::string empty;//empty非显式地初始化为一个空串

一些类要求每个对象都显式初始化,此时如果创建了一个该类的对象而未对其做明确的初始化操作,将引发错误。

3.变量声明和定义

C++语言支持分离式编译,所以C++将声明和定义区分开来。

  • 声明:使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。
  • 定义:负责创建与名字关联的实体(会分配内存空间)。

🚩如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而不要显式地初始化变量:

1
2
extern int i;//声明i而非定义i
int j;//声明并定义j

任何包含了显式初始化的声明即成为定义,但这么做也就抵消了extern的作用:

1
extern double pi=3.1416;//定义

⚠️变量能且只能被定义一次,但是可以被多次声明。

extern只是声明的一种方式,还有其他声明的方式。

基本类型变量的声明和定义(初始化)是同时产生的;而对于对象来说,声明和定义是分开的:

1
2
A a;//就是一个声明,告诉编译器a是A类的一个对象变量,但是不进行初始化
a = new A();//这就是初始化,分配了空间

4.标识符

⚠️C++的标识符由字母数字下划线组成,其中必须以【字母或下划线】开头。

❗️标识符的长度没有限制,但是对大小写字母敏感。

例如:

1
int somename,someName,SomeName,SOMENAME;//定义4个不同的int变量

❗️C++语言保留了一些名字供语言本身使用,这些名字不能被用作标识符。

❗️同时,C++也为标准库保留了一些名字。用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外的标识符不能以下划线开头

C++预留的名字见下:

5.作用域

不论是在程序的什么位置,使用到的每个名字都会指向一个特定的实体:变量、函数、类型等。⚠️然而,同一个名字如果出现在程序的不同位置,也可能指向的是不同实体。

C++语言中大多数作用域都以花括号分隔。同一个名字在不同的作用域中可能指向不同的实体。名字的有效区域始于名字的声明语句以声明语句所在的作用域末端为结束

例如如下代码:

1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
	int sum=0;
	for(int val=1;val<=10;++val)
		sum+=val;//相当于sum=sum+val
	std::cout<<"Sum of 1 to 10 inclusive is "<<sum<<std::endl;
	return 0;
}

这段程序定义了3个名字:mainsumval,同时使用了命名空间名字std,该空间提供了2个名字coutcin供程序使用。

名字main定义于所有花括号之外,它和其他大多数定义在函数体之外的名字一样拥有【全局作用域】。一旦声明之后,全局作用域内的名字在整个程序的范围内都可使用。

名字sum定义于main函数所限定的作用域之内,从声明sum开始直到main函数结束为止都可以访问它,但是出了main函数所在的块就无法访问了,因此说变量sum拥有【块作用域】。名字val定义于for语句内,在for语句之内可以访问val,但是在main函数的其他部分就不能访问它了。

5.1.嵌套的作用域

作用域能彼此包含,被包含(或者说被嵌套)的作用域称为【内层作用域】,包含着别的作用域的作用域称为【外层作用域】

作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问该名字。

❗️允许在内层作用域中重新定义外层作用域已有的名字:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
int reused=42;//reused拥有全局作用域
int main()
{
	int unique=0;//unique拥有块作用域
	std::cout<<reused<<" "<<unique<<std::endl;//输出42 0
	int reused =0;//重新定义变量reused,覆盖了全局变量reused
	std::cout<<reused<<" "<<unique<<std::endl;//输出0 0
	std::cout<<::reused<<" "<<unique<<std::endl;//输出42 0,显式的访问全局变量reused
}

::reused因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。