c++学习(2.2)变量

2.2.1 变量定义

变量定义的基本形式是:首先是类型说明符, 随后紧跟着一个或者多个变量名组成的列标, 其中变量名以逗号分隔, 最后以分号结束。 列表中, 每个变量名的类型都有特定的类型说明符决定。定义时, 还可以给一个或者多个变量赋初值。

int sum = 0, valus,
    units_sold = 0;
Sale_item item;
std::string book{"asdfghj"};

book的定义用到了库函数std::string, 想iostream一样, string也是在命名空间std中定义的, 我们将在第三章中对string类型做更详尽的介绍。 眼下, 只需要了解string是一种可以表示可变长字符序列的字符类型。c++库提供了几种初始化string类型的方法。其中一种是吧字面值拷贝给数据类型就可以了, 因此在上例中, book被初始化为“asdfghj”。

初始值

当对象在创建时获得一个特定的值, 我们说这个对象被初始化了。 用于初始化变量的值可以是任意复杂的表达式。 当一次定义了一个或者多个变量的时候, 对象的名字随着定义也就马上可以使用了。 因此在同一个表达式中, 可以用先定义的变量的值去初始化之后定义的值。

double price = 109.99, discount = price * 0.16;
double saleprice = applyDiscount(price, discount);

在c++语言中, 初始化变量是一个一场复杂的问题, 我们也将反复讨论这个问题。 很多程序员对于称号来初始化变量这样的一种方式倍感困惑, 这种方式容易让人想到初始化时赋值的一种, 然而实际情况中, 初始化和赋值是连个完全不同的操作。。 然而在许多的编程语言中这种差别基本可以忽略不计, 即使在c++语言中对这个区分又是也无关紧要, 所以人们特别容易吧这两者混为一谈, 特别需要强调的是, 这个概念至关重要。 我么会在后面不止一次的提到这一点。

列表的初始化

c++语言定义了初始化的好几种不同的形式, 这也是此时话问题复杂性的一种体现。 例如想要定义一个名为units_sold的interesting变量并初始化为0, 一下4条语句不止一次提及这一点。:

int units_sole = 0;
int units_sold = {0};
itn units_sold{0};
int units_sold(0);

作为c++11的新标准的一部分, 用花括号来初始化变量得到了全面的应用, 在此之前这种初始化仅仅在没写限制的场合中才能使用。 为了和string中的使用命名相同, 我们将这种赋值的方式称之为列表初始化。仙子啊无论是初始化对象还是某些特定的对象, 都可以使用这样一组花括号括起来的初始值了。

当使用雷子类型的变量的时候, 这种初始化形式有一个重要的特点, 如果我们使用列标初始化切初始值存在丢失信息的风险, 这编译器将报错。

long double id = 3.1415926;
int a{id}, b{id}; // 报错转换为执行, 因为存在丢失信息的风险
int c(id), d = id; // 正确执行, 但是确实丢失了部分信息

在上文的代码中, long double 初始化int变量时, 可能会对视数据, 所以编译器拒绝了a和b 的初始化请求。 其中, 至少id的小数部分会被丢失掉, 而且int可能存不下id的整数部分。

刚刚介绍的看起来无关紧要, 毕竟我们不会故意用long double去初始化一个int类型的值。但是像后面介绍的一样, 这种初始化往往会在不经意之间发生, 我们将在3.2.1中详细的对列表初始化进行更详细的介绍。

默认初始化

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

如果是内置类型的变量未被显示初始化, 他们的值由定义的位置决定。 定义于任何函数体之外的变量被初始化为0. 然而这个的一个例外情况是在函数体内部的内部的内置类型变量将不被初始化, 一个未被初始化的类型变量的值是为定义的, 如果试图拷贝或以其他形式访问此类型将引发错误。

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

对大多数类都支持无需显示初始化而定义对象。 而且, 是否允许不经初始化就定义对象也由类自己决定。 如果允许这种行为, 他将决定对象的初始化的值到底是什么

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

std::string empty;
Sales_item item;

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

定义于函数体内部的内置类型的对象如果没有初始化, 则其值未定义。 类的对象如果没有显示的初始化, 则其值由类确定。

·

未初始化的变量含有一个未确定的值, 使用未初始化的变量是一种错误的编程行为, 并且很难调试。 尽管大多数编译器都能对部分未初始化变量的行为提出警告, 但严格的来说, 编译器并未被要求检查此类错误。

建议初始化每一个内置类型的变量。 虽然并非必须这么做, 但是如果我们不能确保初始化后程序的安全, 那么这么做不失为一种简单可靠的方法。

2.2.2变量声明和定义的关系

为了允许将程序拆分成多个逻辑部分来编写, c++语言支持分离式编译机制, 该机制允许将程序风格成若干个文件, 每个文件可以被独立编译。

如果将程序分为多个文件, 则需要有在文件间共享代码的方式。 例如, 一个文件的代码可能需要在另一个文件中定义的变量。 一个实际的例子是, std::cout和std::cin 他们定义于标准库, 却能被我嗯写的程序所使用。

为了支持分离式编译, c++语言将声明和定于区分来。 声明使得名字为程序所知, 一个文件如果想使用别处定义的名字则必须包含对那个名字的生命。 而定义负责创建于名字相关联的实体。
如果想声明规定了变量的类型和名字, 在这一点上, 定义与之相同, 但是除此之外, 定义还要申请空间, 也可能会为变量赋一个初始值。

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

extern int i;// 声明i
int j;//定义j

任何包含了显示初始化的声明即成为定义。 我们能由extern关键字边集变量赋一个初始值, 反思这么也就抵消了extern的作用。 extern语句如果包含初始值就不再是声明, 而变成定义了。

extern double pi = 3.1415;//此次是定义而不是声明

在函数体内部, 如果试图初始化一个由extern关键字标记的变量, 将引发错误。

变量只能被定义一次但是可以被重复声明。

声明和定义的区别看起来也许微不足道, 但是实际上却十分重要, 如果在多个文件中, 使用同一个变量, 就不许将声明和定义分离开。 此时变量的定义必须且只能出现在一个文件中, 而且其他用到该变量的文件必须对其进行声明, 而绝不能重复定义。

关于c++语言对分离式编译的支持我们在2.6.3节中做更详细的介绍

静态类型

c++是一种静态类型语言, 其含义是在编译阶段检查类型, 其中检查类型的过程我们称之为类型检查。
我们已经知道, 对象的类型决定了对象所能参加的运算, 如果试图执行类型不支持的运算, 编译器就会>报错并且不会生成任何的可执行文件。
程序越复杂, 静态类型检查就越有助于帮助发现问题, 然而, 亲啊提是编译器必须知道每一个实体对象
的类型。这就要求我们在使用每个变量之间就声明他的类型。

2.2.3 标识符

c++标识符由字母、数字和下划线组成, 其中必须以字母或者下划线组成, 。 标识符的长度没有限制, 但是对大小写字母敏感。

//定义四个不同的int类型的变量
int somename, someName, SomeName, SOMENAME;

除了这些要求之外, c++还保留了部分关键字来供c++使用, 这些名字不能被用作标识符。

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

变量命名规范

变量命名有许许多多约定俗成的规范, 下面这些规范能有效提高行程序的可读性。

  • 标识符要能体现具体的含义
  • 变量名一般用小写字母
  • 用户自定义的类名一般以大写字母开头
  • 如果标识符由多个单词组成, 则单词间应该有明细那的区分, 如student_loan或者studentLoan。

对于命名规范来说, 若能坚持必将有效

2.2.4名字的作用域

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

作用域(scope)是程序的一部分, 在其中名字有特殊含义。c+语言中, 大多数作用域都可以用花括号分隔。

同一名字在不同的作用域中可能指向不同的实体。名字的有效区域始于名字的声明语句, 以声明语句所在的作用域末端为结束。

如例:

#include<cstdio>

using namespace std;

int main (void)
{
    int sum = 0;
    for(int val = 1; val <= 10; ++val)
    {
        sum += val;
    }
    printf("%d\n", sum);
    return 0;
}

这段程序定义了三个名字: main、sum和val, 同时使用了命名空间名字 std, 该空间提供了两个名字, cout和cin供程序使用。

名字main定义于所有的花括号之外, 它和大多数定义在函数体之外的名字一样, 拥有全局变量, 名字数码定义于main函数所限定的作用域之内, 从声明sum开始吗知道main函数结束为止都可以访问他, 但是出了main函数所在的块就无法访问了, 因此说变量sum拥有块作用域。 名字val定义于for语句内, 在for语句内可以访问val, 但是在main函数的其他部分就不能访问他。

建议:当你第一次使用的时候在定义它
一般来说对象第一次被使用的时候地方附近定义它是一种很好的选择, 因为这样做有助于更好地容易找到变量的定义, 更重要的是, 当变量的定义与它第一次被使用的地方很近时, 我们也会赋给他一个比较合理的初值。

嵌套的作用域

作用域可以彼此包含, 被包含的作用域可以被称为内层作用域, 包含别的作用域的作用域被称之为外层作用域。

作用域中一旦声明了某个名字, 他所嵌套这的所有作用域中都能访问该名字, 同时, 允许在内层作用域中重新定义外层作用域中已有的名字。

#include<iostream>

using namespace std;

int reused = 42;
int main (void)
{
    int unique = 0;
    //访问全局变量reused
    std::cout << reused << " " << unique << std::endl;
    intreused = 0;
    //访问局部变量reused
    std::cout << reused << " " << std::endl;
    //访问全局变量reused
    std::cout << ::reused << " " << std ::endl;
    return 0;
}
// 42 42;
// 0 0;
// 42 0;

输出1:在此条语句之前只有一个reused的定义 所以输出的是全局变量中的reused;
输出2:此条语句使用的是局部变量reused,
输出3:使用域操作符, 来覆盖默认的作用域规则, 因为全局作用域本身并没有名字, 所以当作用域操作符的坐车为空的时候, 向全局变量发出请求获取作用域操作符右侧名字对应的变量。

!如果函数可能用到某全局变量, 则不宜再定义一个同名的局部变量

你可能感兴趣的:(C++)