编译文件:cc -c one.c two.c 生成.o目标文件
链接文件:cc one.o two.o 默认生成 a.out 执行文件
指定生成的可执行文件名 cc -o one one.o
连续编译、链接:cc one.c cc -o one one.c 中间生成的.o文件会被删除,最后生成执行文件
cc one.c two.c 源文件超过一个时,目标.o文件便不会被删除,
以后可以只编译更改的源文件 cc one.o two.o three.c
C语言32个关键字:
auto double int struct break else long switch
case enum register typedef char extern return union
const float short unsigned continue for signed void
default goto sizeof volatile do if while static
常量修饰:const
c 语言中的进制: 二进制:int a = 0B(b)1100 ; 八进制: int b = 027; 十六进制:int c = 0X(x)AF3;
printf(“%d”, a); printf(“a=%d, b=%x”, a, b);
%4d, 若表示的位数大于4,再按原样输出; 若小于4,则补上空格输出4位,, +4空格补左边,-4空格补右边
%.2f 输出两位小数,默认是6位
%p 输出内存的地址,, 如:printf(“变量a的地址是:%p”, &a)
scanf(“%f”, &a); scanf(“%d, %s,%f”, &a,&b,&c); 接收多个时,分隔符可以是任意符号
包括空格、tab、回车
当数值比较小(在char的取值范围内),用char 比用int 更节省内存开销,因为它只占1个字节
char c = ‘A’;与 char c = 65; 是等效的。一个汉字占两个字符,用char是不行的,
需要用字符数组来存储。
局部变量可以在声明时不初始化,后续使用,与java不同;如果后续赋值是可以的,如果后续直接
使用系统赋给它的默认值是不好的,因为这是一个随机的垃圾值,不是0。
可以在基本数据类型的前面加一些修饰符,也有人称之为限定符,一样的意思。
有以下4种类型修饰符:
- short 短型
- long 长型
- signed 有符号型 包括 正负数和0
- unsigned 无符号型 正数和0
// 下面两种写法是等价的
short int s1 = 1;
short s2 = 1;
// 下面两种写法是等价的
long int l1 = 2;
long l2 = 2;
// 可以连续使用2个long
long long ll = 10;
// 下面两种写法是等价的
signed int si1 = 3;
signed si2 = 3;
// 下面两种写法是等价的
unsigned int us1 = 4;
unsigned us2 = 4;
// 也可以同时使用2种修饰符
signed short int ss = 5;
unsigned long int ul = 5;
unsigned char c1 = 10;
signed char c2 = -10;
long double d1 = 12.0;
short int 取值范围 类似java中的 short
long int int
long longlong
C语言的基本语句跟Java中的差不多
- 循环语句(do while、while、for)
- 条件语句(if 、if-else、switch)
- goto语句 (比如在循环外 定义一个标记 Exit:; 在循环内可以 用 goto Exit;跳出循环)
算术运算符
- + 加法运算符
- - 减法运算符,或负值运算符
- * 乘法运算符
- / 除法运算符
- % 模运算符,或称取余运算符,要求%两侧均为整型
关系运算符
- < 小于运算符
- <= 小于等于运算符
- > 大于运算符
- >= 大于等于运算符
- == 等于运算符
- != 不等于运算符
C语言中没有boolean类型
在C语言中,关系运算的结果为"真"就返回1,"假"就返回0
int a1 = 5 >4;// 1
int a2 = 5 <4;// 0
* 还需注意的是,在C语言中,任何非0值都为"真",只有0值才为"假"如:if(-1){}
if (a=0){}
在C语言中,可以不保存关系运算的结果因此,下面的写法是合法的:
int a = 10;
a > 10;
a == 0;
逻辑运算符
- && 逻辑与运算符
- || 逻辑或运算符
- ! 逻辑非运算符
逻辑运算的结果也只有两个:成立就为"真",返回1;不成立就为"假",返回0
复合赋值运算符
- += 加赋值运算符。如a += 3+1,等价于 a = a +(3+1)
- -= 减赋值运算符。如a -= 3+1,等价于 a = a -(3+1)
- *= 乘赋值运算符。如a *= 3+1,等价于 a = a *(3+1)
- /= 除赋值运算符。如a /= 3+1,等价于 a = a /(3+1)
- %= 取余赋值运算符。如a %= 3+1,等价于 a = a %(3+1)
自增运算符和自减运算符
- ++ 自增运算符。如a++,++a,都等价于a = a+1
- -- 自减运算符。如a--,--a,都等价于a = a-1
逗号运算符和逗号表达式
* 逗号运算符主要用于连接表达式,例如:
int a = 9;
int b = 10;
a = a+1 , b = 3*4;
逗号表达式的运算过程是:从左到右的顺序,先计算表达式1,接着计算表达式2,...,最后计算表达式n
* 整个逗号表达式的值是最后一个表达式的值c = (++a, a *= 2, b = a * 5); <==> c=b;
三目运算符:int a = (b >5) ? 10 : 9;
sizeof可以用来计算一个变量或者一个常量、一种数据类型所占的内存字节数。
sizeof(10);
char c = 'a';
sizeof(c);
sizeof(float);
地址
1.计算机中的内存是以字节为单位的存储空间。内存的每一个字节都有一个唯一的编号,这个编号就称为地址。
凡存放在内存中的程序和数据都有一个地址,也就是说,一个函数也有自己的内存地址
2. 当定义一个变量时,系统就分配一个带有唯一地址的存储单元来存储这个变量。比如:
char a = 'A';// A的ASCII值为65
int b = 66;
在16bit编译器环境下,系统为a、b分别分配1个字节、2个字节的存储单元。变量存储单元的第一个字节的地址就是该变量的地址。
可以看出,变量a的地址是ffc3;变量b的地址是ffc1。内存中存储的都是2进制数据
一维数组
定义的形式为:类型 数组名[元素个数]
int a[5];
int [] a; 这样写是错的 不同于java写在变量名前后都行 且定义时需要指定元素个数
初始化:
int a[2] = {8,10};
其实相当于:
int a[2];
a[0] = 8;
a[1] = 10;
元素值列表可以是数组所有元素的初值,也可以是前面部分元素的初值
int a[4] = {2,5}; 这样就赋值给a[0]=2;a[1]=5; a[2]=a[3]=默认值0
当定义并直接初始化时 可以不指定元素个数
int a[] = {2,3,5};
数组初始化时的赋值方式只能用于数组的定义,定义之后只能一个元素一个元素地赋值
int a[3];
a = {1,2,3};//错误。 因为 数组名a 代表变量的地址,是个常量,给常量赋值就错了
当数组作为实参时,因为传递的是一个地址,所以形参改变,实参也会改变
二维数组
定义形式:类型 数组名[行数][列数]
int a[2][3];//共2行3列,6个元素, 即 两个包含三个元素的一维数组
在内存中的存储情况,例如int a[2][2]
(注意:a[0]、a[1]也是数组,是一维数组,而且a[0]、a[1]就是数组名,因此a[0]、a[1]就代表着这个一维数组的地址)
1> 数组a的地址是ffc1,数组a[0]的地址也是ffc1,即a = a[0];
2> 元素a[0][0]的地址是ffc1,所以数组a[0]的地址和元素a[0][0]的地址相同,即a[0] = &a[0][0];
3> 最终可以得出结论:a = a[0] = &a[0][0],以此类推,可以得出a[1] = &a[1][0]
printf("%p\n%p\n%p", a, a[0], &a[0][0]);
二维数组的初始化
* 按行进行初始化
int a[2][3] = { {2,2,3}, {3,4,5} };
* 按存储顺序进行初始化(先存放第1行,再存放第2行)
int a[2][3] = {2,2,3,3,4,5};
* 对部分元素进行初始化
int a[2][3] = { {2}, {3,4} };
int b[3][3] = { { }, { , ,2}, {1,2,3}};
* 如果只初始化了部分元素,可以省略行数,但是不可以省略列数
int a[][3] = {1,2,3,4,5,6};
int a[][3] = {{1,2,3}, {3,5}, {}};
为什么不能只省略列数呢? 因为int a[2][]={1,2,3,4,5,6} 这样无法确定元素在哪一行
字符串简介
* 在Java中,一个字符串可以用String类型来存储
String s = "MJ";
C语言中没有String这种类型。其实字符串就是字符序列,由多个字符组成,所以在C语言中,我们可以用字符数组来存储字符串。
* 字符串可以看做是一个特殊的字符数组,为了跟普通的字符数组区分开来,应该在字符串的尾部添加了一个结束标志'\0'。
'\0'是一个ASCII码值为0的字符,是一个空操作符,表示什么也不干。所以采用字符数组存放字符串,赋值时应包含结束标志'\0'。
* 字符串"mj"的存储情况如下(假设用字符数组char a[]来存储):char a[] = {'m', 'j', '\0'};
注意了,尾部有个'\0',如果没有这个结束标记,说明这个字符数组存储的并不是字符串
字符串的初始化
char a[3] = {'m', 'j', '\0'};
char b[3];
b[0] = 'm';
b[1] = 'j';
b[2] = '\0';
char c[3] = "mj"; //“”初始化时,系统会自动在字符串尾部加上一个\0结束符
char d[] = "mj";
char e[20] = "mj";
使用‘’初始化时,一定记得加‘\0’,
puts函数 输出一个字符串后自动换行,一次只能输出一个字符串到‘\0’结束
char a[]={‘a’,’b’,’\0’}; puts(a); puts(“ab”);
scanf函数
char a[10];系统会自动在尾部加上一个结束标记\0
scanf("%s", a);因为a代表它的地址,所以不需要写成&a,
gets函数
系统会自动在尾部加上一个结束标记\0
char a[10];
gets(a); 一次只能读取一个字符串 ,scanf 读取可以多个
gets可以读入包含空格、tab的字符串,直到遇到回车为止;scanf不能用来读取空格、tab
gets与scanf混用时发现:先gets再scanf没事,若先scanf再gets则gets不执行了
字符串数组简介
* 一维字符数组中存放一个字符串,比如一个名字char name[20] = "mj"
* 如果要存储多个字符串,比如一个班所有学生的名字,则需要二维字符数组,char names[15][20]可以存放15个学生的姓名(假设姓名不超过20字符)
* 如果要存储两个班的学生姓名,那么可以用三维字符数组char names[2][15][20]
字符串数组的初始化
char names[2][10] = { {'J','a','y','\0'}, {'J','i','m','\0'} };
char names2[2][10] = { {"Jay"}, {"Jim"} };
char names3[2][10] = {"Jay","Jim"
字符输出函数putchar
putchar(65); // A
putchar('A'); // A
int a = 65;
putchar(a); // Aputchar一次只能输出一个字符
字符输入函数getchar
char c;
c = getchar();
* getchar函数可以读入空格、TAB,直到遇到回车为止。scanf则不能读入空格和TAB。
* getchar一次只能读入一个字符。scanf则可以同时接收多个字符。
* getchar还能读入回车换行符,这时候你要敲2次回车键。第1次敲的回车换行符被getchar读入,第2次敲的回车键代表输入结束。
strlen函数 返回字符串长度不包括 \0, 计算时直到 \0为止
int
strcpy函数
char s[10];
strcpy(s,"lmj");将右边的"lmj"字符串拷贝到字符数组s中 直到拷贝到\0为止 在s的尾部肯定会保留一个\0
strcat函数
char s[20] = “hello”; // 将右边的字符串拼接到 s的尾部,连接完毕后在s的尾部保留一个\0
strcmp函数
两个字符串从左至右逐个字符比较(按照字符的ASCII码值的大小),直到字符不相同或者遇见'\0'为止。如果全部字符都相同,则返回值为0。
如果不相同,则返回两个字符串中第一个不相同的字符ASCII码值的差。即字符串1大于字符串2时函数返回值为正,否则为负。
strcmp(s1, “abd”);
直接引用
char a;
a = 10;
程序内部是怎么操作的呢?
其实,程序对变量的读写操作,实际上是对变量所在的存储空间进行写入或取出数据。就上面的代码而言,
系统会自动将变量名a转换为变量的存储地址,根据地址找到变量a的存储空间,然后再将数据10以2进制的形式放入变量a的存储空间中。
通过变量名引用变量,由系统自动完成变量名和其存储地址之间的转换,称为变量的"直接引用"方式
间接引用
如 将变量a的地址存放在另一个变量中,比如存放在变量b中,然后通过变量b来间接引用变量a,间接读写变量a的值。这就是"间接引用"
总结一句:用来存放变量地址的变量,就称为"指针变量"。在上面的情况下,变量b就是个"指针变量",我们可以说指针变量b指向变量a。
指针的定义
一般形式:类名标识符 *指针变量名;
int *p;
float *q;
- "*"是一个说明符,用来说明这个变量是个指针变量,是不能省略的,但它不属于变量名的一部分
- 前面的类型标识符表示指针变量所指向的变量的类型,而且只能指向这种类型的变量
指针的初始化
int a = 10;int *p = &a; float b = 2.3f; float *q; q = &b;
指针运算符
给指针指向的变量赋值
char a = 10;
printf("修改前,a的值:%d\n", a);
// 指针变量p指向变量a
char *p = &a;//这个* 是定义指针的说明符
// 通过指针变量p间接修改变量a的值
*p = 9;//这个* 是指针运算符,表示 把9赋值给 指针指向的地址a,也就相当于 a = 9;
//这里 就是间接修改a的值
printf("修改后,a的值:%d", a);
取出指针所指向变量的值
char a = 10;
char *p;
p = &a;
char value = *p;//根据p值(即变量a的地址)访问对应的存储空间,并取出存储的内容(即取出变量a的值),赋值给value
printf("取出a的值:%d", value);
使用注意
在指针变量没有指向确定地址之前,不要对它所指的内容赋值。下面的写法是错误的
int *p;
*p = 10; //这是错误的
应该在指针变量指向一个确定的变量后再进行赋值。下面的写法才是正确的
// 定义2个int型变量
int a = 6, b;
// 定义一个指向变量b的指针变量p
int *p;
p = &b;
// 将a的值赋值给变量b
*p = a;
例子
交换两个字符变量的地址(改变实参的值)
void swap(char *p,char
{
char
*p = *q;
*q = temp;
}
int main(int argc,constchar
{
char a ='A', b ='&';
swap(&a, &b);
printf("a=%c b=%c\n”, a, b);
}
用指针指向一维数组的元素
int a[2] = {2, 3}; int *p = &a[0]; *p = 10; 那么 a[0]应该等于10
数组名a 的地址 与 它的第一个元素的地址相同, 所以 p = &a[0] 与 p = a 效果一样
指针来遍历数组元素
int ary[] = {1,2,3, 4, 5};
int
for (int i =0; i <5; i++)
{
//数组内元素内存地址是连续的存储方式,指针移动一个对应类型单位字节数(int、char...),则指向下一个元素值
// printf("数为:%d ", *(q+i)); //地址移动
// printf("数为:%d ", *(ary+i)); //地址移动
printf("数为:%d ", *(q++));//q=q+1,指针指向的地址移动
// printf("数为:%d ", *(ary++)); //错误,常量不能赋值
}
数组、指针、函数参数
形参数组,实参指针
void change(int b[]) {
b[0] = 10;
}
int main()
{
// 定义一个int类型的数组
int a[4] = {1, 2, 3, 4};
int *p = a;
// 将数组名a传入change函数中
change(p);
// 查看a[0]
printf("a[0]=%d", a[0]);
return 0;
}
形参指针,实参数组
void change(int *b) {
b[0] = 10;
// 或者*b = 10;
b[1] = 11;
// 或 *(b+1) = 11;
}
int main()
{
// 定义一个int类型的数组
int a[4] = {1, 2, 3, 4};
// 将数组名a传入change函数中
change(a);
// 查看a[0]
printf("a[0]=%d", a[0]);
return 0;
} //可以看出,在很多情况下,指针和数组是可以相互切换使用的。但是,并不能说指针就等于数组
用指针遍历字符串的所有字符
char chs[] = "abcde";
char
p = chs;
for (; *p != '\0';p++)
{
printf("data:%c ", *p);
}
printf("\n");
用指针直接指向字符串
char *p = "abcde";
strlen("abde”);
函数在string.h中的声明
size_t strlen(const char
char *strcpy(char *, const char *); // 字符串拷贝函数
char *strcat(char *, const char *); // 字符串拼接函数
int strcmp(const char *, const char *); // 字符串比较函数
它们的参数都是指向字符变量的指针类型,因此可以传入指针变量或者数组名。
指针指向字符串的其他方式
1 char s[10];
2 s = "mj"; //编译器肯定报第2行的错,因为s是个常量,代表数组的首地址,不能进行赋值运算
1 char *s = "mj";
2
3 *s = "like";
第3行代码犯了2个错误:
- 第3行代码相当于把字符串"like"存进s指向的那一块内存空间,由第1行代码可以看出,s指向的是"mj"的首字符'm',
- 也就是说s指向的一块char类型的存储空间,只有1个字节,要"like"存进1个字节的空间内,肯定内存溢出
- 由第1行代码可以看出,指针s指向的是字符串常量"mj"!因此是不能再通过指针来修改字符串内容的!
- 就算是*s = 'A'这样"看起来似乎正确"的写法也是错误的,因为s指向的一个常量字符串,不允许修改它内部的字符。
- char a[] = "lmj";定义的是一个字符串变量! char *p = a; *p = ‘L’; 变量可以通过指针改变,常量不行
- char *p2 = "lmj";定义的是一个字符串常量!
返回指针的函数
返回指针的函数的一般形式为:类型名 * 函数名(参数列表)
// 将字符串str中的小写字母变成大写字母,并返回改变后的字符串
// 注意的是:这里的参数要传字符串变量,不能传字符串常量
char * upper(char *str) {
// 先保留最初的地址。因为等会str指向的位置会变来变去的。
char *dest = str;
// 如果还不是空字符
while (*str != '\0') {
// 如果是小写字母
if (*str >= 'a' && *str <= 'z') {
// 变为大写字母。小写和大写字母的ASCII值有个固定的差值
*str -= 'a' - 'A';
}
// 遍历下一个字符
str++;
}
// 返回字符串
return dest;
}
int
{
// 定义一个字符串变量
char str[] = "lmj";
// 调用函数
char *dest =
"%s", dest);
"%s", str);
return 0;
}
指向函数的指针
定义的一般形式:函数的返回值类型 (*指针变量名)(形式参数1, 形式参数2, ...);
注意:形式参数的变量名可以省略,甚至整个形式参数列表都可以省略
int sum(int a, int
{
return
}
int main()
{
int (*q) (int a, int b) = sum;// (int a, int b) 可以写成(int a, int)或(int,int)或()
int result = (*q)(2, 5);//调用函数
printf("\n%d", result)
return 0;
}
将函数作为参数
void get(int (*q)(int a, char b), float
预处理指令
1.C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),
产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译
2.为了区分预处理指令和一般的C语句,所有预处理指令都以符号"#"开头,并且结尾不用分号
3.预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。
习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件
不带参数的宏定义
#define 宏名 字符串
比如#define ABC 10
右边的字符串也可以省略,比如#define ABC
它的作用是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常用来定义常量
宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef ABC命令
宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误
定义一个宏时可以引用已经定义的宏名
#define R 3.0
#define PI 3.14
#define L 2*PI*R
带参数的宏定义
#define 宏名(参数列表) 字符串
在编译预处理时,将源程序中所有宏名替换成字符串,并且将 字符串中的参数 用 宏名右边参数列表 中的参数替换
#include <stdio.h>
#define average(a, b) (a+b)/2
int main ()
{
int a = average(10, 4); // 替换成 (10+4)/2
printf("平均值:%d", a);
return 0;
}
宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串
#define R (a) (a+5)
void
{
int a = R(5); //这时替换的结果是: int a = (a) (a+5)(5);
"%d\n", a);
}
带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。
所以在定义宏时,一般用一个小括号括住字符串的参数或结果
记住替换的原则是在预编译时
与函数的区别
从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:
1> 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题
2> 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率
预处理指令:条件编译
程序的其中一部分代码只有在满足一定条件时才进行编译,
否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译
一般用法:
#if 条件1
...code1...
#elif 条件2
...code2...
#else
...code3...
#endif//条件编译结束标记
#if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,
宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义
#define MAX 11
int main ()
{
#if MAX == 0
printf("MAX是0");
#elif MAX > 0
printf("MAX大于0");
#else
printf("MAX小于0");
#endif
return 0;
}
是否定义过宏:
#if defined(MAX)//#ifdef的使用和#if defined()的用法基本一致
#elif !defined(MAX)//#ifndef又和#if !defined()的用法基本一致
#endif
预处理指令:文件包含
1.第1种形式#include <文件名>
直接到C语言库函数头文件所在的目录中寻找文件
2.第2种形式 #include "文件名"
系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找
注意:#include指令允许嵌套包含,比如a.h包含b.h,b.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.h,b.h 包含 a.h
使用#include指令可能导致多次包含同一个头文件,降低编译效率
解决方法:在包含的文件里,加上条件编译指令,防止函数被重复定义
如:
#ifndef c_program_cc_h//如果没有定义该宏,那就定义一个,然后预编译函数1,2; 如果定义了则不执行编译跳到了#endif
#define c_program_cc_h
method 1();
method 2();
………….
#endif
局部变量
1> 定义:在函数内部定义的变量,称为局部变量。形式参数也属于局部变量。
2> 作用域:局部变量只在定义它的函数内部有效,即局部变量只有在定义它的函数内部使用,其它函数不能使用它。
全局变量
1> 定义:在所有函数外部定义的变量,称为全局变量。
2> 作用域:全局变量的作用范围是从定义变量的位置开始到源程序结束,即全局变量可以被在其定义位置之后的其它函数所共享。
变量的存储类型
C语言根据变量的存储类型(变量存储的位置:1. 运行时堆栈 2.普通内存 3.寄存器)的不同,
可以把变量分为:自动变量、静态变量、寄存器变量
> 哪些是自动变量:被关键字auto修饰的局部变量都是自动变量,但是极少使用这个关键字,
基本上是废的,因为所有的局部变量在默认情况下都是自动变量。存在堆栈
> 哪些是静态变量:存在内存,程序结束才销毁
- 所有的全局变量都是静态变量
- 被关键字static修饰的局部变量也是静态变量
> 哪些变量是寄存器变量:
- 被关键字register修饰的自动变量都是寄存器变量
- 只有自动变量才可以是寄存器变量,全局变量和静态局部变量不行
- 寄存器变量只限于int、char和指针类型变量使用
extern与函数
- 外部函数:如果在当前文件中定义的函数允许其他文件访问、调用,就称为外部函数。C语言规定,不允许有同名的外部函数。
- 内部函数:如果在当前文件中定义的函数不允许其他文件访问、调用,只能在内部使用,就称为内部函数。
- C语言规定不同的源文件可以有同名的内部函数,并且互不干扰。
> extern修饰的函数就是外部函数,默认情况下,所有的函数就是外部函数 extern void one(); int two();
> 你想要把其他源文件中定义的外部函数拿过来声明,完整的做法,应该使用extern关键字,表示引用别人的"外部函数”:
extern void one();//需要 在 main() 之前引入, 这里的extern 也可以省略
static与函数
在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),
这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。
extern与全局变量
> 默认情况下,一个函数不可以访问在它后面定义的全局变量,若要使它可以访问 则:
extern int a;//extern是用来声明一个已经定义过的变量
void cc() { a = 7; }
int a = 9;
> 声明多个 相同的全局变量 ,表示的是同一个变量 如:
int a;
main() {};
int a;
int a;
> 在局部变量前 加extern
int a;
main()
{
extern int a; //这还是全局变量那个 a
//int a;
}
> 假如在多个源文件中都有全局变量int a;,那么这些源文件的所有全局变量int a;都代表着同一个变量
使用方法 1: 1.c int a 2.c int a
2: 1.c int a 2.c extern int a
3: 1.c extern int a 2.c int a
static与全局变量
如果在定义全局变量的时候加上static关键字,此时static的作用在于限制该全局变量的作用域,
只能在定义该全局变量的文件中才能使用,跟其他源文件中的同名变量互不干扰
结构体
它允许内部的元素是不同类型的
结构体的定义
//结构体类型:
struct
{
char
int
float
};
//定义一个结构体变量,定义变量时才分配存储空间
struct Person
struct
char *name;
int
} stu;
struct { //省略类型,结构体变量名为stu
char *name;
int
} stu;
结构体的注意点
1.不允许对结构体本身递归定义
如下做法是错误的,注意第3行
1 struct
2 int
3 struct
4
2.结构体内可以包含别的结构体
3.结构体变量占用的内存空间是其成员所占内存之和,而且各成员在内存中按定义的顺序依次排列
结构体的初始化
1.
struct
{
char name[20];
int
float
};
struct Person person = {"a", 28, 55.5f};
2.
struct
{
char name[20];
int
float
} person = {"a", 28, 55.5f};
只能在定义变量时 初始化
结构体的使用
person.age = 22;// 这里的.运算符 优先级最高
struct Person p1 = {"a", 28, 55.5f};
struct Person
结构体数组’
跟结构体变量一样,结构体数组也有3种定义方式 ……person[5];
初始化与数组类似 struct Person person = { {}, {}, {} };
结构体作为函数参数
将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,
也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。//值传递
指向结构体的指针
* 结构体指针变量的定义形式:struct 结构体名称 *指针变量名
* 有了指向结构体的指针,那么就有3种访问结构体成员的方式
- 结构体变量名.成员名
- (*指针变量名).成员名//因为 .优先级高,,所以要括起来
- 指针变量名->成员名
void
{
printf("p-name:%s\n", person.name);
struct Person *per = &person;
printf("p-age:%d\n", (*per).age);
printf("p-weight:%.2f\n", per->weight);
}
枚举
它可以用于声明一组常数。当一个变量有几个固定的可能取值时,可以将这个变量定义为枚举类型
枚举类型的定义
enum
枚举变量的定义
跟结构体一样,有3种方式定义枚举变量
> enum
enum
>enum
>enum
枚举使用的注意
> C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量。
> 枚举元素的值取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。
enum
也就是说spring的值为0,summer的值为1,autumn的值为2,winter的值为3
> 也可以在定义枚举类型时改变枚举元素的值
enum
没有指定值的枚举元素,其值为前一元素加1。也就说spring的值为0,summer的值为3,autumn的值为4,winter的值为5
enum
{
18, fall, winter
} season;
void
{ season = spring;//等价于 season = 0; //默认从0开始
season = 9;
season = winter;//等价于 season = 20;
printf("season= %d\n", season);
int *p = &season;
printf("p=%d\n", *p);
}
关键字:typedef
用法:为各种数据类型定义一个新名字(别名)
typedef与基本数据类型
typedef int Integer;Integer a = 8;
也可以在别名的基础上再起一个别名
typedef Integer MyInteger;
原来的数据类型也可以正常使用
typedef与指针
typedef char *String;String str = “stone”;
typedef与结构体
typedef struct Person Per; // 这样在定义结构体变量时 就不用带上struct 关键字了
Per p; p.name = “xyz”;
定义并取别名:
typedef struct Student
{
int
} Stu;
void
{
Stu student = {18};
age = 19;
}
typedef与指向结构体的指针