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语言的封装性,一定程度上实现了信息的封装和隐藏

数组

数组

一维数组

       数组名的值是一个指针常量,也就是数组第一个元素的地址。

       注意数组名为指针常量,而不是指针变量,不能改变。

下标引用

       对于已知的数组长度,编译器将不检查它的下标,因为涉及到位置和长度方面的信息,将会影响程序的执行效率。

       2[array]在有些编译器中的含义为*(array + 2)

指针与下标

       下标绝不会比指针更有效率,但指针有时会比下标更有效率

       当然,指针的高效率前提是被正确地使用。

指针的效率

clip_image002

clip_image004

       从上面的一个数组复制到另一个数组的总结为:

l  当你根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生更高的代码,当这个增量是1并且机器具有地址自动增量模型时,这点表现更为突出;

l  声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高(具体提高的幅度取决于你所使用的机器);

l  如果你可以通过测试一些已经初始化并经过调整的内容来判断循环是否应该终止,那么你就不需要使用一个单独的计数器;

对于使用指针或者数组的判断:

Ø  程序的维护是软件产品的主要成本所在,所以要多考虑一下维护方便;

Ø  有些机器在设计时使用了特殊的指令,用于执行数组下标的操作,目的就是为了使这种极为常用的操作更加快速,在这种机器上,编译器将使用这些特殊的指令来实现下标表达式,但编译器并不一定会用这些指令来实现指针表达式,所以在这种机器上,下标表达式可能会比指针更有效率;

Ø  关于使用哪种方式,最主要的还是要先确认程序中到底是那段代码占用了绝大部分的运行时间,然后把精力用在这些代码上,努力改进它们。

数组和指针

 

 

 

 

 

int a[5];       

      

       会直接分配内存空间。

 

 

?

int *b;       

 

并未被初始化为指向任何现有的内存空间,甚至根本不会被初始化。

作为函数参数的数组名

void strcpy(char *buffer, char const *string);

       该函数中const的优点:

Ø  这是一个良好的文档习惯,通过函数原型就可以发现该数据不会被修改,而不用阅读完整的函数定义;

Ø  编译器可以捕捉到任何试图修改该数据的意外错误;

Ø  这类声明允许向函数传递const参数。

声明数组参数

int strlen(char *string);

int strlen(char string[]);

这两个其实含义是一样的,调用函数时实际传递的是一个指针,所以函数的形参实际上是个指针。但是为了使程序员新手更容易上手一些,编译器也接受数组形式的函数形参。

       但是两个定义中,指针的声明更加准确一些

字符数组的初始化

       当用于初始化一个字符数组时,字符串为一个初始化列表,在其他任何地方,表示一个字符串常量

指向数组的指针

       int matrix[3][10];

       int (*p)[10] = matrix;–指向数组的指针

 

作为函数参数的多维数组

void func(int (*mat)[10]);         指向整型数组的指针

void func(int **mat);             指向整型指针的指针

初始化

       多维数组的初始化最好加上花括号,比如:

int a[2][3][4] =

{

{

{},

{1,2,3,4},

{}

},

{

{},

{1,1,1,1},

{}

}

};

原因如下;

l  利于显示数组的结构;

l  对于不完整的初始化列表,相当有用,比如上面的只初始化其中的部分数据,如果不加花括号,要好好数对位置才行;

数组长度自动计算

       要求是只有第一维可以缺省提供,其余的几维必须显示写出。但其实也可以缺省,主要是由于必须要把子维的数组要达到最长要求才行(即子初始化列表中必须有一个完整的形式),否则还是不对。

总结

       在绝大多数表达式中,数组名的值是指向数组第一个元素的指针。这个规则只有两个例外

l  sizeof返回整个数组占用的字节而不是一个指针所占用的字节;

l  单目操作符&返回一个指向数组的指针,而不是一个指向数组第一个元素的指针的指针。

 

指针和数组并不相等:

l  当我们声明一个数组时,它同时也分配了一些内存空间,用于容纳数组元素;

l  当我们声明一个指针时,它只分配了用于容纳指针本身的空间。

 

只要有可能,函数的指针形参都应该声明为const