变量
变量提供一个具有具体名字的、可供程序操作的存储空间(相当于汇编语言中的寄存器)。
1.变量和对象
变量和对象的区别:
- 变量:每个变量都有其数据类型,数据类型决定所占内存的大小和能参与的运算。
- 对象:对象是指一块能存储数据并具有某种类型的存储空间。
对C++来说,“变量”和“对象”一般可以互换使用。一般来说,对于内置类型创建的叫变量,内置类型创建的叫对象。其实都是一个东西。
2.变量的定义
变量定义的基本形式是:类型说明符+一个或多个变量名组成的列表(其中变量名以逗号分隔,最后以分号结束)
列表中的每个变量名的类型都由类型说明符指定,定义时还可以为一个或多个变量赋值。
如:
int sum=0,value,unit_sold=0; //定义内置类型变量,其中sum和unit_sold初始化为0,value默认初始化
Sales_item item; //定义自定义类型item变量,采用默认初始化
std::string book("0-201-78x");//定义库类型对象string,采用直接初始化方式
3.变量的初始化形式
①声明和初始化和赋值的关系:
- 声明:指定一个变量的名字,仅仅是让编译器知道有这个名字,而没有实际分配空间。
- 初始化:给一个声明后尚未初始化后的变量一个有意义的初始值。
- 赋值:销毁一个变量原来的值,并赋予一个新值。
②变量的初始化形式具有4种形式:
-
直接初始化 :调用与实参匹配的有参构造函数,如:
string s1("hiya"); //直接初始化,s1初始化为"hiya" string s2(10,'c'); //直接初始化的第二种方式,将s2初始化成10个'c'
-
拷贝初始化:使用=初始化一个变量,执行的是拷贝初始化。 如:
string s1="huya"; int units_sold=0;
当初始值只有一个时,使用直接初始化或拷贝初始化都可以,但如果初始值有多个,则一般只能使用直接初始化方式,使用拷贝初始化的话会创建一个临时对象用于拷贝:
//只有一个初始值"abc",拷贝初始化等同于直接初始化 string s1 = "abc"; string s2("abc"); //有多个初始值'c',一般用值初始化,使用拷贝初始化会创建临时变量 string s3(10,'c'); //直接初始化 string s4=string(10,'c'); //必须用这种形式来拷贝初始化
-
列表初始化: 列表初始化在C++11中可以用于以下几种情况
(1)用于数组初始化,如:
int arr[]={1,2,3}; int arr2[]{1,2,3,4};
(2)用于STL容器初始化,如vector,string等,如:
vector<int> v1{1,2,3}; string s{"abc"};
(3)用于new操作等圆括号进行初始化的地方,如:
int *a = new int {3}; double b = double{12.12}; int *arr =new int[] {1,2,3};
(4)用于聚合体的初始化(所有成员都是public的;没有定义任何构造函数;没有类内初始值;没有基类也没有virtual函数),要求**初始值顺序必须和声明顺序一致。**如:
struct Data{ int val; string s; }; Data v = {0, "OK"}; //正确 Data v2 = {"OK", 0}; //错误:初始值顺序必须和声明顺序一致
-
默认初始化:
-
对于内置类型:如果变量定义在所有函数体外,则初始化为0;如果定义在函数体内,则不被初始化没有定义(使用未定义的变量将会产生不确定的后果,编译会报错)
-
对于自定义类型:一般来说都可以无需初始化定义(会调用默认构造函数);如果一些类定义时必须要求促使话则会发生错误。
-
-
值初始化:
值初始化效果:
- 内置类型则零初始化对象
- 数组则值初始化数组的每个元素
- 如果类类型有用户定义的默认构造函数,则执行用户定义的默认构造函数;如果有合成的默认构造函数,则先进行零初始化后再执行合成的默认构造函数。
值初始化用于以下几种情况
(1) 以空的括号或花括号对组成的初始化器创建无名临时对象时,如:
int v1 = int{}; //v1=0 double f1 = double(); //f1=0.0
(2) 用new表达式以空括号或花括号创建堆区动态对象时,如:
int *p1 = new int(); //p1指向的内存值为0 int *p2 = new int[10](); //p2指向的数组每个值都为0 int *p3 = new int[]{10,11}; //p3指向数组有2个元素,分别为10,11
(3)以由空花括号创建自定义对象时, 如:
//有合成的默认构造函数 struct A1{ int v=1; string s; }; //无默认构造函数 stuct A2{ int v; string s; A2(int v){} }; //有自己定义的默认构造函数 struct A3{ int v; string s; A3(){} }; A1 a1{}; //合成默认构造函数零初始化后再初始化,v=0,s为空 A2 a2{}; //错误:类无默认构造函数不能初始化 A3 a3{}; //自己定义的构造函数来进行默认初始化,v随机值,s为空
4.变量声明与定义的关系
为了允许把程序拆分成多个逻辑部分来编写,C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。
为了支持分离式编译,C++将声明和定义区分开来。声明使得名字为程序所知,定义负责创建与名字关联的实体。
如果想要声明一个变量,就要在变量名前加上extern,而不要显示地初始化对象
如:
extern int i; //声明i而非定义i
int j; //声明并定义j
extern int k=1; //声明并定义k
**变量能且只能被定义一次,但是可以被声明多次。**如果在多个文件中使用同一个变量,就必须将声明和定义分离,此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,绝对不能对其进行重复定义。
5.名字的作用域
5.1作用域定义
作用域(也叫块):一般用花括号分隔,名字的有效区域始于名字的声明语句,结束于声明语句所在作用域的末尾。
位于所有花括号之外的作用域称为全局作用域,全局作用域的名字在整个程序的范围内都可以使用。位于花括号之内的作用域称为局部作用域(块作用域),块作用域内的变量只能在块内使用,变量出了局部作用域便会被销毁。
如:
#include<iostream>
using namespace std;
int main(){
int sum=0;
for(int i=0;i<10;++i){ //变量i只存在于for循环内的块区域内
su+=i;
}
cout<<i<<endl; //i不在for循环块内,没有定义i,所以无法访问
}
5.2嵌套的作用域
作用域能彼此包含,被包含(嵌套)的作用域被称为内层作用域,包含着别的作用域的作用域被称为外层作用域。
作用域一旦声明了某个名字,它所嵌套着的所有内层作用域都能使访问该名字。同时允许在内层作用域中重新定义外层作用域已有的名字,这样会屏蔽外层作用域已有名字的定义。
如:
#include<iostream>
using namespace std;
int main(){
double i(3.1415); //main函数的花括号是外层作用域,定义了double的i
int sum=0;
for(int i=0;i<2;++i){ //for循环块内是内层作用域,重新定义了i,屏蔽了外层i的名字
sum+=i;
cout<<i<<' ';
}
cout<<i<<endl; //内层作用域的i变量出了内层作用域被销毁,打印外层作用域变量i
}
结果为:0 1 3.1415
建议:
- 第一次使用变量时再定义它
- 如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量