变量、函数声明的作用

2021-01-19  本文已影响0人  Tenloy

# 写在开头

要点1:编译时,必须要有声明。链接时,必须有定义。

要点2:如果定义的地方在使用的地方之后,或不在一个文件。那使用之前,是否需要再声明一次?如果需要,有没有必要?

# 声明、定义、初始化

## 定义 参考链接

变量定义(definition)是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。在程序中,变量有且仅有一个定义。

格式:数据类型 一个变量名/多个变量名用逗号隔开;

不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为 NULL(所有字节的值都是 0),其他所有变量的初始值是未定义的。

## 声明

变量声明(declaration)是向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序链接时编译器需要实际的变量定义。

变量的声明有两种情况:

  1. 一种是需要建立存储空间的。格式与变量定义相同。如int i; //声明,也是定义
  2. 一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。
    例如:extern int a; //声明,不是定义。说明变量a是在别的文件中定义的,这里只是在声明(数据类型、名称存在)
## 初始化

初始化(Initialization)是指为数据对象或变量赋初值的做法。用于进行初始化的程序结构则称为初始化器或初始化列表。在C/C99/C++中,初始化器是声明器的可选部分,它由一个'='以及其后的一个表达式(或含有多个以','隔开的带圆括号表达式的单一列表)所组成。

在定义变量之后,系统为变量分配的空间内存储的值是不确定的,所以需要对这个空间进行初始化,以确保程序的安全性和确定性

## 注意:

当extern声明的变量右侧手动写有初始化式时,就会被当成定义。

# 前向声明、前向引用两个名词

前向声明(Forward Declaration)是指标识符(如数据类型、变量、函数)声明时还没有给出完整的定义。

前向引用(Forward Reference)是指一个标识符在声明前就被使用。(有的地方会将其视为前向声明的同义词)

有的编译器中函数、全局变量可以前向引用。但局部变量的前向引用一般都是禁止的。C++允许在类成员函数中,前向引用成员属性(即上面的成员函数中使用下面要定义的属性)。

# C/C++为什么禁止前向引用?

## 声明的作用 —— 完成编译

借用函数原型中维基百科的原话:函数原型被广泛应用于C、C++ 语言程序代码的上下文中,通过在头文件中放置函数的前向声明来允许将代码拆分为多个翻译单元。即编译器可以单独编译目标文件的这部分内容,然后由链接器组合成可执行文件或库。

## 前向引用的代价 —— 编译器的复杂度与内存需求

举例:C++允许在类成员函数中,前向引用成员属性(即上面的成员函数中使用下面要定义的属性)。

class C {
public:
   void mutator(int x) { myValue = x; }
   int accessor() { return myValue; }
private:
   int myValue;
};

在此例中,对myValue的两次引用早于它的声明。因此,成员函数accessor不能被编译直到编译器获知成员变量myValue的类型,编译器有责任记住accessor的定义直到它看到myValue的声明.

允许前向引用大大增加了编译器的复杂度与内存需求,并且使它不能成为一次通过型的编译器。

C/C++中,即使函数、全局变量的声明与使用在同一文件中,但只要使用先于声明,使用前,都必须加声明。

C/C++禁止变量、函数的前向引用,OC Java允许函数、全局变量的前向引用

Swift中允许函数、变量的前向引用,而且不用导入头文件:

Swift进行了相关的一些优化,只要在同一个命名空间中的资源都是共享的,而且默认情况下,项目名称就是命名空间

## 禁止前向引用(使用前先添加声明)的好处:

# 记录一个我自己的误区:头文件的作用

## 头文件和源文件的区别

头文件和源文件在本质上没有任何区别。 只不过一般:

之前,一直以为是根据导入的头文件,递归编译同名的源文件.m,没被导入过的头文件,其.m文件就不被编译。前两天突然一细想:那如果就是不导入头文件,通过动态创建对象调用的方法呢,crash?好好的类找不到了?

头文件的作用就是放置一些可重复被各源文件导入的代码:一般是类、函数的声明,变量、宏的声明或定义。

预处理阶段:处理#include指令,将每个源文件内,#include后的文件(一般是头文件)内的内容复制过来,替换#include指令,是递归的。将h文件中所有的内容全部扫描进这个当前的源文件中,形成一个.i文件

编译器在编译时是以源文件为单位进行的(每个源文件都会编译),也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件,在PC上的程序开发,一般都有一个main函数,这是各个编译器的约定。

每个源文件都参与编译链接?那无用代码怎么办,编译过程中的中间代码优化环节,会做相关的优化删除工作。

## 头文件是必须的吗?

不是,C语言中不是,OC中也不是随便创建类时都自动创建.h。

  1. 如果确定这个类不会被别人使用(那创建类还有啥意义?当然,也可以动态使用,根据类名、方法名来调用。闲的,这个问题没意义,只是强调一下头文件的作用(绝大部分场景,是方便让其他文件导入声明)),完全可以将.h中对类的声明挪到.m中,然后将.h删除。

  2. 就算本文件内有函数、变量、宏需要导出给其他文件,也不是必须要头文件。头文件只是方便一次导入多个声明,且方便被复用。如果不想用,自己在每个用到的文件中一个一个写声明也可以的。不是必须要通过导入头文件才能使用外文件的函数/变量等的

即头文件不是必须的:

## 一个源文件对应一个头文件吗?

当然不是。

上一篇 下一篇

猜你喜欢

热点阅读