extern关键字详解
链接地址
用于让其他文件访问某个变量
只有全局变量并且没有被static声明的变量才能声明为extern
在test.c文件中定义变量int global=0;
在头文件test.h中声明这个变量为extern int global;
要使用这个变量的其他文件,只要包含test.h就可以了。
extern和const
想在2.c 中引用1.c的定义
非const变量
//1.c
int bufsize;
//2.c
extern int bufsize;
const变量
//1.c
extern const int bufsize=10;
//2.c
extern const int bufsize;
const变量如果希望其他文件能用,必须主动加上extern,因为
没有被static 和const 修饰变量自动会加上extern
extern其他细节
源文件定义了 char a[10];
另一个文件用 extern char *a;
这样使用会报错
extern 和 static
(1) extern 表明该变量在别的地方已经定义过了,在这里要使用那个变量.
(2) static 表示静态的变量,分配内存的时候, 存储在静态区,不存储在栈上面.
头文件能放的东西
1 #define
2typedef
3extern 变量
4函数声明
三个例外:1)值在编译时就已知的const变量的定义可放在头文件中,如:const int num=10;
2)类的定义可放在头文件中。
3)inline函数。
#ifndef <标识>
#define <标识>
、、、、
、、、、
#endif
可以保证在一个文件里只是定义一次。
<标识> 应该为 文件名大写并且前后加 '_' , 句号也变成 '_'
例如 stdio.h 对应的标识 应该为 _STDIO_H_
声明和定义详细区分
定义和声明是不同的。定义只能出现一次,而声明可以出现多次。
extern int x;变量是声明,并未实际分配地址。
void print(); //函数声明,并未产生实际目标代码
extern int ival=10; //虽然ival声明为extern,但是它初始化了,代表这是个定义。
int x; int x=3; void print() { }; //均为定义。
只在头文件中做声明,真理总是这么简单
Linux内核编码规范
缩进: tab =8个空格
switch 中 case default相对与switch不用缩进
大括号:
非函数的大括号 if switch while for do在一行的末尾写上{
函数的大括号在一行的开始
只有一个语句的if 不加大括号,else必须加
空格:
这些关键字后面加一个空格if, switch, case, for, do, while
二元和三元操作符两侧使用一个空格
变量名:
全局变量的变量要尽可能有描述性(单词全用_分隔)
局部变量的名字要尽可能简单
最好基本别用typedef
函数:
尽可能只做一件事,尽可能短(不超过两个屏幕),
函数的本地变量应该少于(5-10)个
宏:
能写成内联就不写成宏
不写影响控制流的宏
宏函数 避免依赖于一个固定名字的本地变量的宏#define FOO(val) bar(index, val)
比如这里的index
避免 宏函数作为左值
为宏函数加括号
内联
超过3行的函数不要内联
返回值
返回值类型应该统一 0成功 非0失败
或正成功 负失败 ,不要混合使用
易混淆点
int *a[10];//a是数组,成员是指针
int (*a)[10];a是指针,指向数组,成员int
*a->b++ 等于 *(a-》b)++;
可变参数个数函数
#include <stdarg.h> //包含头文件
int func(int a,int b, ...) //...表示可变参数 部分
{
va_list tmp; //由于可变参数部分没有变量名,这一步可以当成是声明一个 变量 名。
va_start(tmp , b); // 第一个参数是上面的变量名,第二个参数 为可变参数的前一个参数名,相当于
//为tmp 赋初值
接收变量=va_arg(tmp,type) // type 是数据类型, char* 、int 、double
//va_arg()返回一个可变参数的值,然后指向下一参数
//获取可变参数,就是不停调用va_arg();
// 注意, va_arg()不会对越界进行提醒,所以,最好传参时就指定 变参个数
va_end(tmp);// 最后表明使用结束
// 一共就4句
}
越界演示
demo:
结果:
字符串拼接
sprintf(buff ,“xxxx %s1 %s2”,s1,s2);
宏定义细节
消除预定义依赖
#undef 取消原有的预定义
然后再使用 #define
扩展:消除依赖
使用ide,高亮所有找到的非基础变量,每找到一个,解决依赖后再高亮,
这样方便检查
#define 中的#和##
一般用于宏函数中。
- “#”
将宏函数传递的变量原封不动的转换成字符串
例如:
#define FUNC(a) #a
printf("%s",FUNC(“sos”))
执行的结果:
“sos” //注意输出的结果中包含了" "(双引号)
#define FUNA(a) #a
#define FUNB(b,c) b ## c
printf("%s",FUNA(FUNB(b,c)))
执行的结果:
FUNB(b,c)
将宏的两个参数连接成一个字符串 (写这条主要是因为和下面的##区别)
#define FUNC(a,b) #a#b
printf("%s",FUNC(mgf,fgm));
执行的结果:
mgffgm
- “##”
将两个宏的参数连接在一起当成一个新的宏
#define FUNC(a,b) printf("%s",a ## b)
char *mgffgm="sos";
FUNC(mgf,fgm);
执行的结果:
sos
#define FUNC(a,b) printf("%d",a ## b)
FUNC(12,13);
执行的结果:
1213
对于没有调用的函数
可以对其只声明,不定义
结构体初始化, 及设置默认值
4种初始化结构体方法
struct init{
int a;
char b;
double c;
char *d;
}
一、定义时初始化
struct init member={1, 'a', 3.14, "abc"};
二、逐个成员赋值
struct init member;
member.a = 1;
member.b = 'a';
member.c = 3.14;
member.d = "abc";
三、指定成员赋值(C风格)
struct init member ={
.a = 1,
.b = 'a',
.c = 3.14,
.d ="abc"
}
四、指定成员赋值(C++风格)
struct init member = {
a : 1,
b : 'a',
c : 3.14,
d : "abc"
}
指定默认值方法
struct init{
int a;
char b;
double c;
char *d;
init ()={
.a = 1,
.b = 'a',
.c = 3.14,
.d = "abc"
}
}
注意,结构体定义时,成员间声明用 ‘;’ 隔开,而在赋值时用 ‘,’ 隔开。
结构体可以直接进行赋值操作
对于取成员操作符(->) 的理解
c库中有一个宏函数 offsetof(type, member-designator)
作用是找到结构体成员距离结构首地址的距离
实现为:
offsetof(s, m) (size_t)(&((s*)0)->m)
s 为结构体,m为成员,
含义为:将0,解释为地址为0的 结构体s,接着找到其成员m,然后取m的地址,然后转换成size_t类型
执行的结果就是m相对于结构s的偏移地址。
如果把上述的0改成1,得到的结果就为 m相对与s的偏移地址+1
顺带的,关于linux内核函数container_of(ptr,type,member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
memset 只适合初始化成 0 或 -1
switch case 语句,在C中 case 中不能定义变量,C++可以
函数细节
strncmp(str1,str2,n); 如果n>str1的长度,会出现段错误
//避免方法:将常量放在str1的位置,n=strlen(str1)