const static and extern

C语言中 const static extern

并非常量的const修饰符

const用法:定义常量,修饰指针、函数的输入参数和返回值,简单说const表示只读的意思,本质上来说它只是在全局数据段或者栈中定义的是一个只读的常量,不是真正位于字符串常量区。Const的目的是为了产生高质量的代码,提高代码的可读性,同时保护好不能被任意改变的内存,从而降低Bug 生的概率。

const关键字,很多人想到的可能是const常量,其实关键字const并不能把变量变成常量!在一个符号前加上const限定符只是表示这个符号 不能被赋值。也就是它的值对于这个符号来说是只读的,但它并不能防止通过程序的内部(甚至是外部)的方法来修改这个值(C专家编程.p21)。也就是说 const变量是只读变量,既然是变量那么就可以取得其地址,然后修改其值。看来const也是防君子不防小人啊!🙂

clip_image001

const 使用情况分类详析

l  比如const int n = 10;的意思很明显,n是一个只读变量,程序不可以直接修改其值。

l  const用于指针

n  const int *p

n  int const *p

n  int * const p
在最后的一种情况下,指针是只读的(p只读),而在另外两种情况下,指针所指向的对象是只读的(*p只读)const 是一个左结合的类型修饰符,它与其左侧的类型修饰符一起为一个类型修饰符,所以,int const 限定 *p,不限定pint *const 限定p,不限定*p
。这里有一个简便的区分方法:

沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量

l  const用于函数的地址传递参数void foo(const int *p)这种形式通常用于在数组形式的参数中模拟传值调用。也就是相当于函数调用者声称:我给你一个指向它的指针,但你不能去修改它。如果函数编写者遵循了这个约定,那么就相当于模拟了值传递。这也是const最有用之处了:用来限定函数的形参,这样该函数将不会修改实参指针所指的数据。这里注意了,是函数不应该去修改而不是不能修改,也就是说const不能阻止参数的修改(原因见上)

l  const用于限定函数的返回值:const int foo();上述写法限定函数的返回值不可被更新,当函数返回内部的类型时,已经是一个数值,当然不可被赋值更新,所以,此时const无意义,最好去掉,以免困惑。当函数返回自定义的类型时,这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。

引用其他文件变量的修饰符extern

extern用法:在别的文件中定义的变量,要想在本文件中使用,必须先用extern声明,例如:extern a;之后就当成在本文件中定义的变量一样使用。

clip_image002

封装效果的修饰符static

clip_image003

static用法:

l  修饰变量,从生存域和访问域两个方面说明,无论static变量定义在函数内或外,该变量都位于数据段中;定义于函数体外的static变量的访问域仅仅是它所在文件中定义的函数,其他文件无法通过extern对其声明后访问。

l  修饰函数,使得函数的访问域仅仅为其所定义的函数。

l  类中变量用static修饰表示变量是类变量,类中函数用static修饰表示函数只能访问类中的static变量,不接受this指针,称为类函数。

总之,static实现了c语言的封装性,一定程度上实现了信息的封装和隐藏

函数

函数

函数定义

       把函数类型和函数名分写两行纯属风格问题。这种写法可以是我们在使用视觉或某些工具程序追踪源代码时更容易查找函数名。

原型

       旧式的K&R C只记住函数的返回值类型,而不保存函数的参数数量和类型信息。

函数的缺省认定

       当程序调用一个无法见到的原型的函数时,编译器便认为该函数返回一个整型值。

函数的参数

       C函数的所有参数均以传值调用方式进行传递,这意味着函数将获得参数值的一份拷贝。对于传址调用其实也是传值调用,只不过间接引用的使用时的看起来不像传值调用而已。

ADT和黑盒

       ADTabstract data type抽象数据类型,可以限制函数和数据定义的作用域。这个技巧也被称为黑盒black box设计。ADT使得程序的各个部分之间相互更加独立。

       限制对模块的访问可以通过static关键字的合理使用来实现。

递归与迭代

       许多问题都是以递归的形式进行解释的,这只是因为它比非递归形式更为清晰,但是,这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性可能稍差一些。

       比如最著名的Fibonacci数列,在计算Fibonacci30)的时候,Fibonacci3)的值被计算了317811次,这些值完全一样,是巨大的浪费。

数据

数据

       变量的三个属性:作用域、链接属性和存储类型,决定了该变量的可视性(也就是它可以在什么地方使用)和生命期(它的值将保持多久)。

基本数据类型

       C语言中,仅有4种基本数据类型:整型、浮点型、指针和聚合类型(如数组和结构等)。所有其他的类型都是从这4中基本类型的某种组合派生而来的。

整型家族

       听上去长整型要比短整型所能表示的值要大,但是这个假设并不一定正确。规定整型值相互之间大小的规则很简单:

       长整型至少应该和整型一样长,而整型至少应该和短整型一样长。

       而对于int是选择多少位的缺省值是由编译器的设计者决定的,一般都会选择最高效的位数。

       缺省的char要么是signed char,要么是unsigned char,这取决于编译器,这个事实意味着不同机器上的char可能拥有不同范围的值。所以只有当程序所使用的char型变量的值位于signed charunsigned char的交集中,这个程序才是可移植的。例如,ASCII字符集中的字符都是位于这个范围之内的。

       如果一个多字节字符常量的前面有一个L,那么它就是款字符常量(wide character literal),比如:L’X’

       枚举enumerated常量类型就是指它的值为符号常量而不是字面值的类型,它以下面的形式声明:

       enum month{Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec}; 

浮点类型

浮点数字面值在缺省情况下是double类型的。

指针

       指针是C语言为什么这么流行的一个重要原因。指针可以有效地实现诸如treelist这类高级数据结构。用C语言可以比使用其他语言编写出更为紧凑和有效的程序。但同时,C对指针使用的不加限制正是许多令人欲哭无泪和咬牙切齿的错误的根源。

基本声明

       用于修改变量的长度或是否为有符号的关键字为:

short long signed unsigned

       如果声明中已经至少有了一个其他的说明符,关键字int就可以省略。

声明简单数组

       C数组一个值得关注的地方是,编译器并不检查程序对数组下标的引用是否在数组的合法范围之内,这种不加检查的行为有好处也有坏处,好处是不需要浪费时间对有些已知是正确的数组下边进行检查,坏处是这样做将使无效的下标引用无法被检测出来

隐式声明

       函数如果不显示地声明返回值的类型,它默认返回整型。

Typedef

       typedef允许为各种数据类型定义新名字。在原来的声明前面加上typedef就可以为数据类型定义新名字。比如typedef char * ptr_to_char使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明。你应该使用typedef而不是#define来创建新的类型名,因为后者无法正确地处理指针类型

常量const

int *pi;一个普通的指向整型的指针;

int const *pci;一个指向整型常量的指针;

int *const cpi;一个指向整型的常量指针。

作用域

       编译器可以确认4中不同类型的作用域:文件作用域、函数作用域、代码块作用域和原型作用域。

代码块作用域

       声明于每个代码块的变量无法被另一个代码块访问,因为它们的作用域并无重叠之处,由于两个代码块的变量不可能同时存在,所以编译器可以把它们存储于同一个内存地址。

链接属性

       链接属性一共有3种:external外部、internal内部和none无。

       没有链接属性的标识符none总是被当做单独的个体,也就是说该标识符的多个声明被当做独立不同的实体。属于internal链接属性的标识符在同一个源文件内的所有声明中都指向同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。

       在一个变量声明前加上一个static就可以将它的链接属性变为internal

       如果你在一个地方定义变量,并在使用这个变量的其他源文件的声明中添加external关键字,会更容易理解。

存储类型

       变量的存储类型storage class是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。

       对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在,而不仅仅在声明它的代码块的执行时存在。注意函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。

       而对于register变量,如果声明过多,编译器会只选取前几个实际存储在寄存器中,其余的就按照普通自动变量处理。

       在有些计算机中,如果把指针声明为寄存器变量,程序的效率将能得到提高,尤其是那些频繁执行间接访问操作的指针。

       寄存器变量的创建和销毁时间和自动变量相同,但需要一些额外的工作,即要保存寄存器的原始状态。在许多机器的硬件实现中,并不为寄存器指定地址,同样,由于寄存器值的保存和恢复,某个特定的寄存器在不同的时刻所保存的值不一定相同,基于这些理由,机器并不向你提供寄存器变量的地址

       自动变量如果不初始化,那么它们的值总是垃圾。

static关键字

       当位于不同的上下文环境时,static关键字具有不同的意思。

l  static用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不受影响,用这种方式声明的函数或变量只能在声明它们的源文件中访问;

l  static用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,当变量的链接属性和作用域不受影响,用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。

l  所以,static不改变作用域,改变的是链接属性(代码块外或函数)或存储类型(代码块内)

总结

       具有external链接属性的实体在其他语言的术语里称为全局global实体,所有源文件的所有函数均可以访问。

     如果一个变量声明于代码块内部,在它前面添加extern关键字将使它所引用的是全局变量而非局部变量。

       具有external链接属性的实体总是具有静态存储类型。

       局部变量有函数内部使用,不能被其他函数通过名字引用,它在缺省情况下的存储类型为自动,这是基于两个原因:

1.        当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量;

2.        在堆栈上为它们分配存储可以有效地实现递归。