关键字(register、static、consit、extern、typedef)

1、register
【注意1】register的作用
register的作用是请求编译器尽可能的将变量保存到cpu的内部寄存器中,省去了cpu从内存中获取数据的时间,提高了程序的运行效率,
一般我们将频繁被访问的变量、循环体变量用register修饰。
【注意点】
1、register只能修饰局部变量,不能修饰全局变量和函数。
2、不能用&来获取register变量的地址。
3、register修饰的变量类型必须是cpu接受的。

这里写图片描述

register作用实现的原理:
参考:
浅析c语言关键字register
C语言关键字auto与register的深入理解

【扩展】扩展:CPU组成,计算机系统构成,数据处理流程
需要了解。(以后总结一下)

2、static
【总结static的作用】
static既能修饰变量又能修饰函数。
static修饰局部变量,使b变量保存到数据区,从而使这个变量的生命周期延长至整个程序执行后释放。
static修饰全局变量,修饰的变量只能在本文件访问你,而不能在其他文件访问。
static修饰的函数只能在本文件访问,在必须放在访问之前。
这里写图片描述

3、consit
大家看到const关键字, 大多数人会不会认为它是一个const常量, 我告诉大家这是错误的认知, 那么它实际的意义是什么呢? 其实const不能把变量变成常量, 而是把一个变量, 变成了一个只读变量, 所以我们不能直接修改它的值, 但是我们可以通过它的地址, 来修改它的值.

  1. const的普通用法

// const int n = 5;

int const n = 5; // 这两句话的作用是一样的, 都是声明了一个名为a的只读变量, 我们不可以直接修改a的值.

int a[n] = {1, 2, 3, 4, 5}; // 要求数组的大小是一个常量, 也就是说是一个确定的值, 而n是一个只读变量, 也就是说在n初始化时就有了一个确定的值, 所以可以用来当作数组的大小

  1. const用于指针

int a = 5;
int b = 10;
//const int * p = & a;
int const * p = & a; // 这两句话的作用也是一样的, 都是声明了一个名为p的常量指针, 也就是说p所对应的存储空间是只读的, 我们不能通过p来改变其中的内容

p = &b; // 正确的写法

  • p = 20; // 错误的写法, 因为const修饰是*p, 所以*p是只读的, 我们不能向只读的存储空间中写入数据(p所指向的b是一个变量, 而不是一个常量, 但是这也不行)

b = 20; // 正确的写法, 因为b是一个变量, 没有const修饰b

int * const q = &a; // 作用: const位于*的右侧, 声明q是一个指针常量

q = & b; // 错误的写法, q是一个只读的指针变量, 我们不可以对其重新赋值

  • q = 20; // 正确的写法, 因为*q前并没有const修饰, 所以q指向的存储空间还是可读写的, 所以*q是一个变量, 而不是一个常量, 我们可以通过q来修改对应存储空间中的内容

b = 20; // 正确的写法

大家要看一下const所在的位置是不同的, 所以起到的作用也是不同的, 大家要多我思考. 总结: 1. const在* 左侧, 则不能改的是p, 也就是说不能这样赋值: p = 20; 2. const在*右侧, 则不能改的是p, 也就是说不能这样赋值: p = & b;

  1. const的几种情况:

1)

int main()
{
  const int a = 5;
  int * p = (int *)&a;
  *p = 10;
  printf(“%d\n”, a);
  printf(“%d\n”, *p);

}

// 我们发现打印出来a是值是5, *p的值是10. 为什么会出现这样的情况呢? 难道是存储空间会存储不同的两个值? 当然不会, 因为编译器在预处理时, 只对const修饰的变量值读取一次, 所以打印出来a是5, 实际上我们通过指针变量p改变了a的值, 所以实际上a的值是10. 总结: 1. const修饰的局部变量存储在堆栈中, 我们可以通过指针修改它的值. 2. 编译器只对const修饰的变量的值读取一次, 就是在预处理的时候. 3. 如果a是一个const修饰的全局变量, 则这样写就不可以了, 因为它的值是存储在全局存储空间中, 其值只有可读属性, 不能修改.

也就是说下面这样写是不可以的:

const int a = 5;

int main()
{
  int * p = (int *)&a;
  *p = 10; // 这样写就是错误的了.
  printf(“%d\n”, a);
  printf(“%d\n”, *p);
}

2)
int a = 5;
int b =10;
const int * const p = & a; // 指针本身和指针所指向的存储空间都是只读的.

p = & b; // 错误的写法, 因为p前面有const修饰, 所以p是一个指针常量, 也就是说p的值是不能被重新赋值的.

  • p = 20; // 错误的写法, 因为*p前面有const修饰, 所以*p是一个常量, 也就是说p是一个常量指针, 它所指向的存储空间是只读的.

【总结】
1、consit修饰的只是变量,将变量变为只读变量,可以通过其他方法修饰变量的对应的空间,比如利用指针。
2、consit修饰的变量定义时一定要初始化。
3、consit离谁近就修饰谁。

4、extern(外部声明)
在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。

  1. extern修饰变量的声明。举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明extern int v,还取决于变量v本身是能够被引用到的。这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

  2. extern修饰函数声明。从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

  3. 此外,extern修饰符可用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

【总结】extern声明文件在其他文件定义。
C语言extern关键词—最会带帽子的关键字

5、typedef

在C和C++编程语言中,typedef是一个关键字,它用来对一个类型起一个新名字,也用来声明自定义数据类型,其实给一个类型起新名字的作用也包含在声明自定义数据类型这个功能中。
1.给一个类型起一个新名字

给一个类型其新名字,有时候可以帮助我们更好地记忆。

例子:typedef int inter;

此声明定义了一个 int 的同义字,名字为 inter。注意 在这里typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 inter,即可以用inter来代替int进行整数变量的定义。

这个功能是最常用的,而且相对来说是比较简单的。

2.定义新的类型

定义新的类型有多种形式,下面简单的列一些。

1>typedef BaseType NewType [arrSize]

这种类型可以掩饰一些符合类型,其中BaseType是基本类型,NewType是我们所定义的新类型,这个新定义的NewType可以像其他的基本类型那样使用。下面举个例子:

typedef char Array[10];

这里的char就是基本的类型,而Array是我们新定义的类型。这里Array是一个字符型的数组类型,这个数组类型的长度为10。下面我们就可以用Array来进行一些定义了。

Array array1,array2;

这里我就定义了两个Array型的数组,这两个数组都是字符型的有10个元素的数组;如果我们没有用typedef定义,那么我么就要进行下面这样的定义:

char array1[10];char array2[10]。

这种形式可以应用到指针等。

这里引入typedef一个陷阱:

typedef char * pstr;
int mystrcmp(pstr, pstr);

我们知道,标准函数 strcmp()有两个”const char *”类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():
  
  int mystrcmp(const pstr, const pstr);
  
用GNU的gcc和g++编译器,是会出现警告的,按照顺序,”const pstr”被解释为”char* const”(一个指向 char 的常量指针),两者表达的并非同一意思。应该按以下方式定义:

typedef const char* pstr;

2>typedef int (PF) (const char , const char *)这种类似的形式

这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。这种定义的用途过会在下面以例子的形式给出。

typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。这并不是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。这一点对于我们理解typedef定义新类型的功能很有用。

这里引入typedef另外一个陷阱:

typedef register int FAST_COUNTER;

编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。

3>typedef与结构体结合使用

struct var {
int data1;
int data2;
char data3;
};
这里定义一个类型var,而要定义这种类型的变量,必须这样写:struct var a;若添加typedef struct var newtype;则定义变量只需这样即可:newtype a;

typedef和结构体一般不这样使用,而是按下面这样子:

typedef struct var {
int data1;
int data2;
char data3;
} newtype;

newtype a;

在链表中更一般的形式:

  typedef struct tagNode
  {
  char *pItem;
  struct tagNode *pNext; //这里不能写为*pNode *pNext;
  } *pNode;

或者 typedef struct tagNode *pNode;
  struct tagNode
  {
  char *pItem;
  pNode pNext;
  };

typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以获得最高的精度:
  typedef long double REAL;
  在不支持 long double 的机器上,该 typedef 看起来会是下面这样:
  typedef double REAL;
  并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:、
  typedef float REAL;

linux内核中typedef的例子:

前面提到的”2>typedef int (PF) (const char , const char *)”这种类似的形式是可以简化函数的,而且便于理解。举linux内核中信号处理函数这个例子:

void (*signal (int signr,void (*handler)(int))) (int)

其用typedef定义如下:

typedef void sigfunc(int);

sigfunc *signal(int signr,sigfunc *handler);

其中typedef定义了一个有一个整型参数无返回值的函数类型。void (*handler)(int)表示一个有一个整型参数无返回值的函数指针,这个指针名为handler,所以其可以用sigfunc进行说明,此时sigfunc就相当于前面的int signr中int的作用;同理这个函数也是这样。

注:对复杂变量建立一个类型别名的方法很简单,你只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。

  int (*a[5])(int, char);
  //pFun是我们建的一个类型别名
  typedef int (*pFun)(int, char);
  //使用定义的新类型来声明对象,等价于int* (a[5])(int, char);
  pFun a[5];

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值