当前课程知识点:基于Linux的C++ >  第五讲 程序组织与开发方法 >  5.5 作用域与生存期 >  LinuxCPP0505

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP0505在线视频

LinuxCPP0505

下一节:LinuxCPP0506

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP0505课程教案、知识点、字幕

第三个部分就是作用域和生存期

同学们了解了这两个概念呢

就对理解程序代码会非常非常有用

先来看量的作用域和可见性

作用域就是标识符的有效范围

定义了一个标识符 那么这个标识符

从它的定义处开始到语句块的结束

就叫标识符的有效范围

这个就叫作用域

跟它相关的概念称为可见性

可见性就是我可以看见它

程序中的某一个位置

能不能够使用标识符

就叫标识符的可见性

需要特别注意的地方是什么呢

对于任何标识符来说

只能在作用域内可见

但是在它的作用域内

标识符不一定可见 特别注意这一点

所以说作用域和可见性紧密相关

但是并不一样

局部数据对象

定义在一个函数或一个复合语句块内

具有块的作用域

它的作用域就从它的定义处开始

到这个语句块的结尾

就是因为这一点

不同的函数可以定义同名的变量

比如说Add这个函数

里面可以定义变量t

然后Compare函数里面还可以定义变量t

Swap函数里边还可以定义变量t

都没有问题

就是因为作用域它是不一样的

你在你的空间里 它在它的空间里

两者是不交叉的

如果是在一个函数内

想定义同名的变量 那肯定就不行了

我们来看这样的例子

一个函数func带两个参数x和y

返回值是int

在这个函数内部定义局部变量t

然后把t赋值为x + y

注意后面又来了一个

孤零零的花括号对

它前面也没有if 也没有switch

也没有for 也没有while 没关系

就孤零零一个复合语句块 可以

c允许你这么写 C++还允许你这么写

那么它就给你构造了一个内部的

复合语句块

int n初始化成2

n作为一个局部变量

在一个内部的嵌套的复合语句块里

所以作用域就从定义处开始

到语句块结尾 也就是cout << n

这一条语句做完

那个n值就无效了

t呢从定义处开始一直到return t

整个这个函数内部你都可以用

这就是局部数据对象的作用域

另外一个方面就是全局数据对象

一旦把这样的量定义在任何一个函数

或复合语句块之外 就是全局量

所有的全局量都具有文件作用域

它的有效性就从定义处开始

一直到文件结尾

中间任何函数都可以用它

所以称它为具有文件作用域

有时候也称全局作用域

有一点需要同学们特别注意

如果有这样的全局量

定义在特定的文件里

而这个文件被别人包含了

那么作用域就会扩展出去

谁包含了它

它的作用域就会扩展到

包含它的宿主文件里面去

所以不要在头文件中

定义全局数据对象

因为放在头文件里

非常容易被别人包含

一被别人包含作用域就会扩展过去

哪怕你真需要 现在也不能这么写

后面会告诉你怎么写才对

因为被别人一包含 作用域就过去了

编译器十之八九会报错

因为不允许定义同名的全局变量

在你的整个工程项目里面

不允许定义同名的全局变量

哪怕这个全局变量在这个文件里

另外一个全局变量

是在另外一个文件里

它们不允许同名

接下来是函数原型作用域

写了一个函数原型

函数原型里有一些参数

这些参数具有函数原型作用域

也就是 有效性就从定义处开始

一直到函数原型结束

这就叫函数原型作用域

它不会延展到函数所对应的

实现里面去

所以这就意味着函数原型里面

形式参数的那个名字

和函数真正实现的时候

那个参数的名字可以不一样

我们来看这个例子

同学们一旦理解了这个例子

那么作用域和可见性这个概念

就算基本掌握了

先看第1行 int i

i这个变量定义在任何一个

函数或语句块之外 所以它是全局量

有效性就从定义处开始

第1行代码到文件结尾

也就是第23行 可见性不一定

第2行定义了一个函数原型func

从第3行一直到第10行是main函数

第5行在main函数里边

定义了一个局部量n

那么作用域就从定义处开始

一直到main函数结尾

然后i初始化成10

这是第6行

访问的就是第1行定义的i

全局变量i

然后cout输出 输出i和n的值

n当然就是第5行n

i还是第1行的i 然后n赋值为func(i)

i作为实际参数调用一次func

返回值给n 这也没有问题

再输出i、n

还是访问的局部量的n和全局量的i

然后在第11行定义了全局量n

那么作用域就从它的定义处开始

一直到文件结尾

第12行开始定义func函数

第14行i赋值为0

访问的当然还是全局量

然后cout输出i、n

i还是全局量

n当然是在第11行定义的全局量的n

如果你没有对它进行初始化

所有的全局量都自动地初始化为0

第16行n赋值为20 没有问题

第11行的n被赋值了20

在第17行和第20行之间

是新的复合语句块

第18行int i初始化为n + x 这个可以

注意n是第11行的全局量n

i就是它自个儿

第18行里定义的这个i

x是func函数它的形式参数

第12行出现的x

cout输出 i就是第18行的i

n就是第11行的n

在这个位置

事实上有两个i可供你使用的

一个是局部量的i 一个是全局量的i

第一行i的作用域

包括了第19行代码

第18行定义的i

它的作用域是一直到第20行的

那么这个时候

就会产生一个重要的问题

就是两个不同的量

在这一行上都是可以访问的

那你说这个i访问到底是哪一个i啊

作用域它都在嘛 那就体现一个点

可见性

第一行定义的全局变量i

作用域很长

第19行在这个作用域里面 不可见

原因就在于它被一个新定义的

作用域更小的局部量i覆盖了可见性

这是非常非常重要的一个地方

在这里 第19行访问的i

就是第18行的i

绝不是第1行的i

你可能就会说

那么有没有什么招

第19行里就想访问第1行的i呢

在C++里面提供了一个“::”操作符

叫做全局解析操作符

写一个 ::i

那么访问的就是全局量的i

单写i访问的就是第18行的i

这个叫全局解析

接下来就是存储类和生存期

生存期是量在程序中存在的时间范围

通俗地讲就是这个量能活多长时间

C程序和C++程序都使用存储类

来表达生存期

刚才讲作用域、可见性

表达的是量的空间概念

现在讲存储类和生存期

表达量的时间概念

C和C++里面使用存储类来表达生存期

也就是量在程序中存在的时间范围

一般来讲

这个C/C++代码里面的量具有两类生存期

一个称为静态生存期

一个称为自动生存期

静态生存期有时候也称为全局生存期

全局数据对象一般都具有静态生存期

也就是说它的生生死死

就只和程序是不是运行有关

程序一运行它就出生了

程序一结束它就死掉

整个生存期是跨越程序始终的

在整个程序运行期间它都是有效的

这是非常非常重要的一个概念

第二个就是自动生存期

称它为局部生存期

它的生生死死 只和这个程序

有没有进入这个语句块有关

程序代码进入语句块

在语句块内部定义的局部量

就算出生了

离开了这个局部块 局部量就死掉了

执行一次

这样的局部量就会出生一次 死一次

出生一次 死一次

就是按照这样方式来进行运作的

所以程序每次进入这样的语句块里边

它创建的静态局部量都是不一样的

虽然它们叫同样的名字

但事实上是两个不同的东西

每次进入语句块就会为这样的局部量

分配一个存储空间

每次离开就会销毁它的存储空间

释放内存

所以从某种程度上来讲

套用过去富有哲理的一句话

叫做“人不能两次踏进同一条河流”

两次进入语句块的时候

使用的量不是同一个

接下来呢

有一个非常非常特殊的关键字

称它为static

它用于修饰局部变量的时候

会让局部变量具有静态的生存期

本来嘛 标准的局部变量

具有自动的生存期

可是前面加了static关键字之后

就会让它变成静态生存期

程序在退出块的时候

这样的量并不会消失

当程序下一次进入块的时候

将会使用原先的量继续参与运算

并且使用上一次退出块的时候

设定的值

因为static修饰的局部变量的生命

就像全局量一样得长

它的初始化动作

必须做一些特别的规定

必须进行初始化

静态局部量只改变生存期

不改变作用域

有效性仍然局限在那个地方

所以说生命被拉长了

但是存在的空间仍然没有发生变化

如果用static去修饰全局量

那么意义 就和修饰局部量完全不一样

已经是全局量了

再用static关键字

来拉长它的时间特性有意义吗

用static修饰全局量的时候

不表示将生存期拉长

而表示限定作用域

只在本文件内部使用

其它文件不可见

先来看静态局部量的例子

有一个主函数 还有一个func

在这个func函数里边定义静态局部量count

把它初始化成0 然后输出count值

递增count值之后return

在主函数里边写for循环

然后输出 调用func函数

count就是计数器

记录func被调用了多少次

局部量是没用的

每次运行count 值都会初始化成0

但是静态局部量写在这个地方

初始化为0

那也不意味着每次调用func

都会初始化成0

这个静态局部量看上去就像全局量一样

初始化在程序启动前就做好

调用main函数之前

count就会被初始化成0

所以进入func函数的时候

这个count值变成了1

第2次进入的时候

它将在1的基础上继续递增成2

第3次就会继续递增成3

所以可以来记录func被调用了多少次

如果没有静态局部量技术

定义一个全局量在外面不就可以了吗

但是定义成全局量

就没有和func关联起来

现在有了静态局部量

事实上可以把count这个量写在函数里

这一点和刚才那肯定不一样

这样程序设计逻辑要合理得多

对于一个函数

它也有自己的作用域和生存期

所有的函数都具有文件作用域

和静态生存期

也就是说 这些函数有效范围尽可能大

时间尽可能得长

可有的时候还想写一些函数

就想自己用 不想给别人用

哪怕写一个库

库里面提供了一系列的接口

写在库对应的头文件里 你就可以用

但是我还为了这些库

内部还实现了自己的一些函数

外面接口已经够你用的了

剩下这些函数不想给你用

就可以把这些函数定义成内部函数

函数分成内部的和外部的两个部分

内部函数就是不可以被其它文件用的

外部函数就可以被其它文件中的函数

所调用的

缺省情况下 所有的函数都是外部函数

具有文件作用域和静态生存期

要想让它变成一个内部函数

要在这个函数前边加上static关键字

它就是内部函数

不能再把它写在这个库

所对应的头文件里

特别需要注意

当我们在创造、引入一个类型、

一个量的时候

有两个概念 一个叫声明 一个叫定义

这两者不是一回事

定义是在程序中产生新的实体

而声明呢我们不创造它 只是引入它

这个实体可能是别人替我们创造的

可能是我们在另外一个文件中创造的

声明和定义是不一样的

对于函数来讲

声明就是给出函数的原型

定义就是给出函数的编码实现

如果产生一个新的类型

那就叫定义

如果新的类型没有被产生

那就是声明

定义一定会构造出新的型来

而声明是没有构造出来

看我类型定义的例子

typedef enum __BOOL { FALSE, TRUE }

后面跟着BOOL

一个新的类型 名字叫enum __BOOL

这就是新的枚举型

有两个可能的取值 FALSE和TRUE

这个类型的构造是清晰的、完整的

后面又用typedef把它定义成新的型BOOL

等价于enum __BOOL

它的型也是完整的

所以就创造了一个新的类型

那你看下面这一行

enum __BOOL创造了一个新的类型吗 没有

这个类型写在这个地方

定义是不完整的 后面缺了花括号对

没跟着枚举文字的序列就不全

意味着这一条语句仅仅给出了类型

enum __BOOL一个声明

而没有给出它的定义

接下来回到前面遗留的问题

如果是个全局量 加上static

那就表示别人不能用了

不加static 全局量别人能用吗

在一个文件里面定义了它

工程项目的另外一个文件能够用它吗

是不能的

本身就是一个程序

只是为了便于组织分成很多个文件

这个量整个程序里边都要用到它

那这个量定义在一个文件里

另外一个文件不能用

程序咋写啊

就把这个量写在头文件里

不行 千万记得不要在头文件里

定义全局数据对象

工程项目可能有很多个源文件

每一个源文件都可能#include头文件

只要头文件里面定义全局变量

那么工程项目中

每一个包含头文件的文件

都会把变量的定义写进去

这就意味着工程项目中的好几个文件

都会定义同名的全局变量

编译器是通不过的

它会认为你这是错的

想要这个量跨文件能够使用

又不能够重复定义

那怎么办呢 有一个技巧就在这里

这个关键字叫extern

要用它才能解决这个问题

你想定义这个量

那么你就把这个量定义在库的源文件里

然后在头文件里写上“extern int a;”

就表示这条语句将变成变量的声明

而不是变量的定义

它将把整型变量a导出

所有包含头文件的文件

就自动包含了“extern int a;”这一行

就表示它会将一个整型变量a

导入到它的空间里边去

凡是包含了这个头文件的文件

就可以使用整型变量a了

使用的就是在这个库的源文件里定义的a

解决了变量的重复定义的问题

所以同学们一定要会用这个关键字extern

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP0505笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。