1.extern的用法
1.1简单理解
简单的c知识:
如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
extern放在变量或者函数之前,表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,如此可以不用导入变量或者函数所在的文件。
1.2用法实例
/****max.c****/
#include <stdio.h>
/*外部变量声明*/
extern int g_X ;
extern int g_Y ;
int max()
{
return (g_X > g_Y ? g_X : g_Y);
}
/***main.c****/
#include <stdio.h>
/*定义两个全局变量*/
int g_X=10;
int g_Y=20;
int max();
int main(void)
{
int result;
result = max();
printf("the max value is %d\n",result);
return 0;
}
1.3extern的注意事项
(1)在一个源文件里定义了一个数组:char a[6],在另外一个文件里用下列语句进行了声明:extern char *a是不可以的,编译可以通过,但运行时出现错误。
原因:指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]。
(2)extern全局变量如果在一个test1.h头文件中将全局变量的声明和定义放在一起,extern char str[] = “123456”; // 这个时候相当于没有extern.在两个.cpp文件中都有包含这个.h头文件,这时候再编译连接test1.cpp和test2.cpp两个模块时,会报连接错误,这是因为你把全局变量的定义放在了头文件之后,test1.cpp这个模块包含了test1.h所以定义了一次g_str,而test2.cpp也包含了test1.h所以再一次定义了g_str,这个时候连接器在连接test1和test2时发现两个g_str。如果你非要把g_str的定义放在test1.h中的话,那么就把test2.cpp的代码中#include "test1.h"去掉 换成在变量定义前面加上extern:extern char str[];这个时候编译器就知道g_str是引自于外部的一个编译模块了,不会在本模块中再重复定义一个出来,但是这样做非常糟糕,因为你由于无法在test2.cpp中使用#include “test1.h”,那么test1.h中声明的其他函数你也无法使用了,除非也用都用extern修饰,这样的话你光声明的函数就要一大串,所以 请记住:只在头文件中做声明,真理总是这么简单。
2.static关键字
推荐文章
这个关键字可以参考这个文章,讲的挺好的。
3.const关键字
3.1简单介绍
看到关键字const时,首先想到的应该是:只读。因为,它要求其所修饰的对象为常量,不可对其修改和二次赋值操作(不能作为左值出现)。但并不是这么简单的。
如下:
#include<iostream>
using namespace std;
int test()
{
const int m_A = 20;
cout<<"m_A = "<<m_A<<endl;
int *p = (int *)&m_A;//强制类型转换
*p=200;
cout<<p<<" "<<&m_A<<endl;
cout<<m_A<<endl;
cout<<"此时m_A = "<<m_A<<endl;
return 0;
}
int main()
{
test();
return 0;
}
由图和代码可知
尽管p和&m_A指向同一个地址,但const修饰的变量并不可以通过变量名或内存地址更改更改。
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int b = 2;
cout<<"-------int *const c类型的更改---------"<<endl;
int *const c=&a;
cout<<a<<" "<<b<<" "<<*c<<" "<<endl;
*c=50;
cout<<"a = "<<a<<" "<<"b = "<<b<<"c = "<<*c<<" "<<endl;
cout<<"-------int const * c类型的更改--------"<<endl;
const int *d=&a;
*c = 200;
cout<<"a = "<<a<<" "<<" "<<"b = "<<b<<" "<<"d = "<<*d<<endl;
return 0;
}
由此可以看出int const和int const * 类型的值是可变的。
但是 int * const 的变量只可以通过指针修改变量的值。
而int const * 类型的变量只可以通过指针的方式改变变量的值。
4.define关键字
用途一:
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:
char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针,
// 和一个字符变量;
以下则可行:
typedef char* PCHAR; // 一般用大写
PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针
虽然:
char *pa, *pb;
也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的地方,typedef的方式更省事。
用途二:
用在旧的C的代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名 对象名,如:
struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
而在C++中,则可以直接写:结构名 对象名,即:
tagPOINT1 p1;
估计某人觉得经常多写一个struct太麻烦了,于是就发明了:
typedef struct tagPOINT
{
int x;
int y;
}POINT;
POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候
或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。
用途三:
用typedef来定义与平台无关的类型。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
typedef long double REAL;
在不支持 long double 的平台二上,改为:
typedef double REAL;
在连 double 都不支持的平台三上,改为:
typedef float REAL;
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
用途四:
为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:
- 原声明:int *(a[5])(int, char);
变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int *(pFun)(int, char);
原声明的最简化版:
pFun a[5];
- 原声明:void (b[10]) (void ()());
变量名为b,先替换右边部分括号里的,pFunParam为别名一:
typedef void (*pFunParam)();
再替换左边的变量b,pFunx为别名二:
typedef void (*pFunx)(pFunParam);
原声明的最简化版:
pFunx b[10];
- 原声明:doube(*)() (*e)[9];
变量名为e,先替换左边部分,pFuny为别名一:
typedef double(*pFuny)();
再替换右边的变量e,pFunParamy为别名二
typedef pFuny (*pFunParamy)[9];
原声明的最简化版:
pFunParamy e;
理解复杂声明可用的“右左法则”:
从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:
int (*func)(int *p);
首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int类型的形参,返回值类型是int。
int (*func[5])(int *);
func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。
也可以记住2个模式:
type (*)(…)函数指针
type (*)[]数组指针
第二、两大陷阱
陷阱一:
记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:
先定义:
typedef char* PSTR;
然后:
int mystrcmp(const PSTR, const PSTR);
const PSTR实际上相当于const char吗?不是的,它实际上相当于char const。
原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。
简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。
陷阱二:
typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:
typedef static int INT2; //不可行
编译将失败,会提示“指定了一个以上的存储类”。
以上资料出自:http://blog.sina.com.cn/s/blog_4826f7970100074k.html 作者:赤龙