C++Primer 变量

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!
在这里插入图片描述
在这里插入图片描述

目录

    • 2.2变量
      • 变量定义
      • 初始值
      • 列表初始化
      • 默认初始化
      • 提示:未初始化变量引发运行时故障
      • 变量声明和定义的关系
      • 关键概念:静态类型
      • 标识符
        • 变量命名规范
      • 名宇的作用域
      • 嵌套的作用域

2.2变量

变量提供一个具名的、可供程序操作的存储空间。C++中的每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。对C++程序员来说,“变量(variable)“和“对象(object)“一般可以互换使用。

变量定义

变量定义的基本形式是:首先是类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。列表中每个变量名的类型都由类型说明符指定,定义时还可以为一个或多个变量赋初值:

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

book的定义用到了库类型std::string,像iostream-样,string也是在命名空间std中定义的。C++库提供了几种初始化string对象的方法,其中一种是把字面值拷贝给string对象,因此在上例中,book被初始化为0-201-78345-X。

初始值

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

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

在C++语言中,初始化是一个异常复杂的问题,我们也将反复讨论这个问题。很多程序员对于用等号=来初始化变量的方式倍感困惑,这种方式容易让人认为初始化是赋值的-种。事实上在C++语言中,初始化和赋值是两个完全不同的操作。然而在很多编程语言中二者的区别几乎可以忽略不计,即使在C++语言中有时这种区别也无关紧要,所以人们特别容易把二者混为一谈。需要强调的是,这个概念至关重要,我们也将在后面不止一次提及这一点。

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

列表初始化

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

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

作为C++11新标准的一部分,用花括号来初始化变量得到了全面应用,而在此之前,这种初始化的形式仪在某些受限的场合下才能使用。这种初始化的形式被称为列表初始化(list initialization)。现在,无论是初始化对象还是某些时候为对象赋新值,都可以使用这样一组由花括号括起来的初始值了。

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

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

使用long double的值初始化int变量时可能丢失数据,所以编译器拒绝了a和b的初始化请求。其中,至少1d的小数部分会丢失掉,而且int也可能存不下1d的整数部分。刚刚所介绍的看起来无关紧要,毕竟我们不会故意用long double的值去初始化int变量。

默认初始化

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

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

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

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

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

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

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

提示:未初始化变量引发运行时故障

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

使用未初始化的变量将带来无法预计的后果。有时我们足够幸运,一访问此类对象程序就崩溃并报错,此时只要找到崩溃的位置就很容易发现变量没被初始化的问题。另外一些时候,程序会一直执行完并产生错误的结果。更糟糕的情况是,程序结果时对时错、无法把握。而且,往无关的位置添加代码还会导致我们误以为程序对了,其实结果仍然有错。

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

变量声明和定义的关系

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

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

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

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

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

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

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

extern double pi=3.1416; //定义

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

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

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

关键概念:静态类型


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

我们巳经知道,对象的类型决定了对象所能参与的运算。在C++语言中,编译器负责检查数据类型是否支持要执行的运算,如果试图执行类型不支持的运算,编译器将报错并且不会生成可执行文件。

程序越复杂,静态类型检查越有助于发现问题。然而,前提是编译器处须知道每一个实体对象的类型,这就要求我们在使用每个变量之前必须声明其类型。

标识符

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

//定义4个不同的int变量
int somename,SomeName,SomeName,SOMENRME;

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

变量命名规范

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

标识符要能体现实际含义。

  • 变量名一般用小写字母,如index,不要使用Index或INDEX。
  • 用户自定义的类名一般以大写字母开头,如Salesitem。
  • 如果标识符由多个单词组成,则单词间应有明显区分,如student_1oan或StudentLoan,不要使用Studentloan.。

表2.3:C++关键字

alignascontinuefriendregtstertrue
alignofdecltypegotoreinterpret_casttry
asmdefaultifreturntypedef
autodeleteinlineshorttypeid
booldointsignedtypename
breakdoublelongsizeofunion
casedynamic_castmutablestaticunsigned
catchelsenamespacestatic_asssertusing
charenumnnewstatic_castvirtual
char16_texplicitnoexceptstructvoid
char32_texportnullptrswitchvolatile
classexternoperatortemplatewchar_t
constfalseprivatethiswhile
constexprfloatprotectedthread_local
const_castforpublicthrow

表2.4:C++操作符替代名

andbitandcomplnot_eqor_eqxor_eq
and_eqbitornotorxor

名宇的作用域

不论是在程序的什么位置,使用到的每个名字都会指向一个特定的实体类型等。然而,同一个名字如果出现在不同的位置也可能指向不同的实体。作用域(scope)是程序的一部分,在其中名字有其特定的含义。C++语言中大多数作用域都以花括号分隔。

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

一个典型的示例来自于如下程序:

#include<iostream>
int main()
{
    int sum=0;
    //sum用于存放从1到10所有数的和
    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个名字:main、sum和val,同时使用了命名空间名字std,该空间提供了2个名字cout和cin供程序使用。名字main定义于所有花括号之外,它和其他大多数定义在函数体之外的名字一样拥有全局作用域〔(globalscope)。一旦声明之后,全局作用域内的名字在整个程序的范围内都可使用。名字sum定义于main函数所限定的作用域之内,从声明sum开始直到main函数结束为止都可以访问它,但是出了main函数所在的块就无法访问了,因此说变量sum拥有块作用域(Cblockscope)。名字val定义于for语句内,在for语句之内可以访问val,但是在main函数的其他部分就不能访问它了。

嵌套的作用域

作用域能彼此包含,被包含(或者说被崴套)的作用域称为内层作用域(inner scope),包含着别的作用域的作用域称为外层作用域(outer scope)。

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

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

输出#1出现在局部变量reused定义之前,因此这条语句使用全局作用域中定义的名字reused,输出420。输出#2发生在局部变量reused定义之后,此时局部变量reused正在作用域内(in scope),因此第二条输出语句使用的是局部变量reused而非全局变量,输出0 0。输出#3 使用作用域操作符来覆盖默认的作用域规则,因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。结果是,第三条输出语句使用全局变量reused,输出420。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948292.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【模型】Qwen2-VL 服务端UI

1. 前言 最近在测试VLM模型&#xff0c;发现官方的网页demo&#xff0c;代码中视频与图片分辨率可能由于高并发设置的很小&#xff0c;导致达不到预期效果&#xff0c;于是自己研究了一下&#xff0c;搞了一个简单的前端部署&#xff0c;自己在服务器部署了下UI界面&#xff0…

分布式事务介绍 Seata架构与原理+部署TC服务 示例:黑马商城

1. 什么是分布式事务? 在分布式系统中&#xff0c;如果一个业务需要多个服务合作完成&#xff0c;而且每一个服务都有事务&#xff0c;多个事务必须同时成功或失败&#xff0c;这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务。 打个比…

uni-app:实现普通选择器,时间选择器,日期选择器,多列选择器

效果 选择前效果 1、时间选择器 2、日期选择器 3、普通选择器 4、多列选择器 选择后效果 代码 <template><!-- 时间选择器 --><view class"line"><view classitem1><view classleft>时间</view><view class"right&quo…

C++Primer 基本类型

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

纯前端实现将pdf转为图片(插件pdfjs)

需求来源 预览简历功能在移动端&#xff0c;由于用了一层iframe把这个功能嵌套在了app端&#xff0c;再用一个iframe来预览&#xff0c;只有ios能看到&#xff0c;安卓就不支持&#xff0c;查了很多资料和插件&#xff0c;原理基本上都是用iframe实现的。最终转换思路&#xf…

基于FPGA的出租车里程时间计费器

基于FPGA的出租车里程时间计费器 功能描述一、系统框图二、verilog代码里程增加模块时间增加模块计算价格模块上板视频演示 总结 功能描述 &#xff08;1&#xff09;&#xff1b;里程计费功能&#xff1a;3公里以内起步价8元&#xff0c;超过3公里后每公里2元&#xff0c;其中…

Unix 域协议汇总整理

Unix 域协议是一种用于同一台计算机上进程间通信&#xff08;IPC&#xff09;的技术。它提供了一种比基于网络的套接字更高效的方式来在本地进程中交换数据。Unix 域协议使用文件系统作为通信的媒介&#xff0c;并且只限于在同一台计算机上运行的进程之间进行通信。 Unix 域套接…

JVM学习:CMS和G1收集器浅析

总框架 一、Java自动内存管理基础 1、运行时数据区 运行时数据区可分为线程隔离和线程共享两个维度&#xff0c;垃圾回收主要是针对堆内存进行回收 &#xff08;1&#xff09;线程隔离 程序计数器 虚拟机多线程是通过线程轮流切换、分配处理器执行时间来实现的。为了线程切换…

1.C语言教程:历史、特点、版本与入门示例

目录 1.历史2.特点3.版本4.编译5.Hello World 示例 1.历史 本篇原文为&#xff1a;C语言教程&#xff1a;历史、特点、版本与入门示例。 更多C进阶、rust、python、逆向等等教程&#xff0c;可去此站查看&#xff1a;酷程网 C 语言的诞生源于 Unix 系统的开发需求。 1969 年…

lec7-路由与路由器

lec7-路由与路由器 1. 路由器硬件 路由器的硬件部分&#xff1a; 断电失去&#xff1a; RAM断电不失去&#xff1a;NVRAM&#xff0c; Flash&#xff0c; ROMinterface也算是一部分 路由器是特殊组件的计算机 console 口进行具体的调试 辅助口&#xff08;Auxiliary&…

spring防止重复点击,两种注解实现(AOP)

第一种&#xff1a;EasyLock 简介 为了简化可复用注解&#xff0c;自己实现的注解&#xff0c;代码简单随拿随用 使用方式 1.创建一个注解 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface EasyLock {long waitTime() default …

Linux-Ubuntu之I2C通信

Linux-Ubuntu之I2C通信 一&#xff0c;I2C通信原理1.写时序2.读时序 二&#xff0c;代码实现三&#xff0c;显示 一&#xff0c;I2C通信原理 使用I2C接口驱动AP3216C传感器&#xff0c;该传感器能实现两个效果&#xff0c;一个是感应光强&#xff0c;另一个是探测物体与传感器…

音视频入门基础:MPEG2-PS专题(4)——FFmpeg源码中,判断某文件是否为PS文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.ps 可以判断出某个文件是否为PS文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为PS文件呢&#xff1f;它内部其实是通过mpegps_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_format3函数和AVI…

框架模块说明 #09 日志模块_01

背景 日志模块是系统的重要组成部分&#xff0c;主要负责记录系统运行状态和定位错误问题的功能。通常&#xff0c;日志分为系统日志、操作日志和安全日志三类。虽然分布式数据平台是当前微服务架构中的重要部分&#xff0c;但本文的重点并不在此&#xff0c;而是聚焦于自定义…

【数据仓库】hadoop3.3.6 安装配置

文章目录 概述下载解压安装伪分布式模式配置hdfs配置hadoop-env.shssh免密登录模式设置初始化HDFS启动hdfs配置yarn启动yarn 概述 该文档是基于hadoop3.2.2版本升级到hadoop3.3.6版本&#xff0c;所以有些配置&#xff0c;是可以不用做的&#xff0c;下面仅记录新增操作&#…

算法题(25):只出现一次的数字(三)

审题&#xff1a; 该题中有两个元素只出现一次并且其他元素都出现两次&#xff0c;需要返回这两个只出现一次的数&#xff0c;并且不要求返回顺序 思路: 由于对空间复杂度有要求&#xff0c;我们这里不考虑哈希表。我们采用位运算的方法解题 方法&#xff1a;位运算 首先&#…

将机器学习预测模型融入AI agent的尝试(一)

将机器学习临床预测模型融入AI agent的尝试&#xff08;一&#xff09; 我主要是使用机器学习制作临床预测模型和相关的应用&#xff0c;最近考虑的事情是自己之前的的工作能不能和AI agent进行融合&#xff0c;将AI 对自然语言理解能力和预测模型的预测能力结合在一起&#x…

51单片机——按键实验

由于机械点的弹性作用&#xff0c;按键开关在闭合时不会马上稳定的接通&#xff0c;在断开时也不会一下子断开&#xff0c;因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的&#xff0c;一般为 5ms 到 10ms&#xff0c;为了确保 CPU 对按键的…

电子邮件对网络安全的需求

&#xff08; 1&#xff09;机密性&#xff1a;传输过程中不被第三方阅读到邮件内容&#xff0c;只有真正的接收方才可以阅读邮件。&#xff08; 1.5 分&#xff09; &#xff08; 2&#xff09;完整性&#xff1a;支持在邮件传输过程中不被篡改&#xff0c;若发生篡改&#…

vue路由模式面试题

vue路由模式 1.路由的模式有哪些?有什么区别? history和hash模式 区别: 1.表现的形态不同: 在地址栏url中:hash模式中带有**#**号,history没有 2.请求错误时表现不同: 在hash模式中,对于404地址请求时,不会进行请求 但是在history模式中,对于404请求时,仍然会进行请求…