0

C++ Primer学习笔记03:变量

 2 years ago
source link: https://direct5dom.github.io/2022/03/13/CPP-Primer%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B003/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

C++ Primer学习笔记03:变量

Posted on

2022-03-13 In CPP Primer学习笔记

CPP.png

变量提供一个具名的、可提供程序操作的存储空间。

对C++程序员来说,“变量(variable) ”和“对象(object) ”一般可以互换使用。

通常情况下,C++中的对象指的是一块能存储数据并具有某种类型的内存空间。

基本形式为:类型说明符(type specifier)后紧跟一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。

列表中每个变量名的类型都由类型说明符指定,定义时还可以为一个或多个变量赋初值:

int sum = 0, value,		// sum、value 和 units_sold 都是 int
units_sold = 0; // sum 和 units_sold 初值为 0
Sales_item item; // item 的类型是 Sales_item
// string 是一种库类型,表示一个可变长的字符序列
std::string book("0-201-78345-X"); // book 通过一个 string 字面值初始化

book的定义用到了库类型std::string,像iostream一样,string也是在命名空间std中定义的,是一种表示可变长字符序列的数据类型,在这里被初始化为0-201-78345-X

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

int sum = 0;	// sum 的初值为 0

被定义的对象名字也可以被使用,先定义的变量值可以用于初始化后定义的其他变量。

// 正确:price 先被定义并赋值,随后用于初始化 discount
double price = 109.99, discount = price * 0.16;
// 正确:调用函数 applyDiscount,然后用函数的返回值初始化 salePrice
double salePrice = applyDiscount(price, discount);

注意:虽然初始化和赋值的操作很接近,但是在C++中二者是完全不同的两个操作,不能被混为一谈。

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

列表初始化

C++定义了初始化的好几种形式,这也是初始化问题复杂性的一个体现。例如想要定义一个名为units_soldint变量并初始化为0,以下4条语句都可以做到:

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

C++11新标准中,用花括号{}来初始化变量得到了全面应用,此前这种方法仅能在某些受限场合下使用。

使用花括号{}初始化的形式被称为列表初始化(list initialization) 。无论是初始化对象还是为对象赋新值,都可以使用花括号{}

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

long double ld = 3.1415926536;
int a{ld}, b = {ld}; // 错误:转换未执行,因为存在丢失信息的危险
int c(ld), d = ld; // 正确:转换执行,且确实丢失了部分值

使用long double的值初始化int变量时可能丢失数据,所以编译器拒绝了ab的初始化请求(至少小数部分会丢掉,且int不一定能存下ld的整数部分)。虽然我们不会故意这么去做,但是可能无意间发生这种错误。

默认初始化

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

如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。

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

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

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

std::string empty;		// empty 非显式的初始化为一个空串
Sales_item item; // 被默认初始化的 Sales_item 对象

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

变量声明和定义的关系

C++支持分离式编译(separate compilation) 机制,允许程序将程序分割为若干文件,每个文件可被独立编译,这可以允许程序员把程序拆成多个逻辑部分来编写。

程序被拆分为多个文件时,需要有在文件间共享代码的方法。例如,一个文件的代码可能需要使用另一个文件中定义的变量。比如std::coutstd::cin,它们定义于标准库,却能被我们写的程序使用。

为了支持分离式编译,C++语言将声明和定义区分开来。声明(declaration) 使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition) 负责创建于名字关联的实体。

变量声明规定了变量的类型和名字,这与定义相同。但定义除此之外还申请存储空间,也可能为变量赋一个初始值。

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

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

任何包含了显式初始化的声明即成为定义。

我们可以给extern标记的变量赋初值,但是这样做也就抵消了extern的作用。extern语句如果包含初始值就不再是声明,而是定义。

extern double pi = 3.1416;	// 	定义

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

如果在多个文件中使用同一个变量,就必须将声明和定义分离。变量能且只能被定义一次,但是可以被多次声明。

C++是一个静态类型(statically type) 语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查(type checking)。

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

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

C++保留了一些名字提供语言本身使用,即关键字。关键字不能被用作标识符。

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

定义在函数体外的标识符不能以下划线开头。

变量命名规范

变量命名有许多约定俗成的规范,下面这些规范能有效提高代码的可读性:

  • 标识符要能体现实际含义。
  • 变量名一般用小写字母,如index,不要使用IndexINDEX
  • 用户自定义的类名一般以大写字母开头,如Sales_item
  • 如果标识符由多个单词组成,则单词间应有明显区分,如student_loanstudentLoan,不要使用studentloan

名字的作用域

C++中使用到的每个名字都指向一个特定的实体:变量、函数、类型等。然而在不同的作用域(scope) 中可能指向不同的实体。

一个典型案例:

#include <iostream>
int main()
{
int sum = 0;
for (int val = 1; val <= 10; ++val)
sum += val;
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}

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

定义于所有花括号之外(如main)的名字拥有全局作用域(global scope) ,可以在整个程序范围内使用。

定义于一部分花括号内(如sum,在main函数的花括号内)的名字拥有块作用域(block scope) ,只能在定义开始到花括号结束}中使用,程序的其他部分无法访问。

建议:第一次使用变量时再去定义。

嵌套的作用域

被包含(被嵌套)的作用域称为内层作用域(inner scope)

包含着别的作用域的作用域称为外层作用域(outer scope)

作用域中声明的名字,它所嵌套的作用域都能访问改名字。同时,允许内层作用域中重新定义外层作用域已有的名字:

#include <iostream>
// 该程序仅用于说明:函数内部不宜定义与全局变量同名的新变量
int reused = 42; // reused 拥有全局作用域
int main()
{
int unique = 0; // unique 拥有块作用域
// 输出#1:使用全局变量 reused;输出 42 0
std::cout << reused << " " << unique << std::endl;
int reused = 0; // 新建局部变量 reused,覆盖了全局变量 reused
// 输出#2:使用局部变量 reused;输出 0 0
std::cout << reused << " " << unique << std::endl;
// 输出#3:显式的访问全局变量 reused;输出 42 0
std::cout << ::reused << " " << unique << std::endl;
return 0;
}

输出#2 发生在局部变量reused定义之后,此时局部变量reused正在作用域内(in scope)

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK