初识C语言 (2)
#1. 关键字
前言:C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的。
1.1static关键字
1.1.1 static修饰局部变量
我们来看下边一段示例代码:
void test() { int a = 5; a++; printf("%d ", a); } int main() { int i = 0; while (i < 10) { test(); i++; } return 0; }
这段代码会打印10个6,为什么呢?因为a这个变量是定义在test这个函数内部的,是个局部变量。每次调用test函数时,a变量会被重新创建并且被赋初始值为5,再经过++操作变成6被打印,当test函数调用完毕时,a这个局部变量又会被销毁。当下一次调用时,a又会被重新创建并被赋初始值为5,这样一直重复10次。
假若我们用static修饰a变量呢?
void test() { static int a = 5; a++; printf("%d ", a); } int main() { int i = 0; while (i < 10) { test(); i++; } return 0; }
此刻,就会在屏幕上打印6-15.
原因:被static修饰的局部变量,该变量的空间直到整个程序运行结束(main函数执行完毕)才会被销毁。当第一次调用test函数时,a被创建赋初始值为5,++之后变成6被打印,但是这次结束调用时,a变量的空间并不会被销毁,并且a的值为6,当第二次调用test函数时a的值仍然为6,并且a的地址不会发生变化,经过++操作之后,a变为7……以此重复10次。
注意:被static修饰的局部变量在编译阶段就被创建了。
但是为什么被static修饰之后,局部变量的空间就变得“长期有效”了呢?
解释:在c/c++中,内存空间被大致分为了三部分:
栈区:存放局部变量,形参,以及 具有临时作用的变量,栈区变量的特点是,进入作用域就创建,出了作用域就被销毁。
堆区:用于动态内存分配的(malloc,realloc,calloc)
静态区:存放全局变量,静态变量,静态区的变量的特点是:创建之后,直到程序结束才被销毁
所以:局部变量本来是存放在栈区的,但是被static修饰的局部变量就被放在了静态区了,static修饰局部变量,改变了变量的存储类型,使局部变量的生命周期变长了,但是作用域不变,直到程序结束才结束。
1.1.2 static修饰局部变量
我们在一个程序中创建两个源文件:
test1.c
int g_val = 10;
test2.c
#include<stdio.h> extern int g_val; int main() { printf("%d", g_val);//打印10 return 0; }
假如test1.c中的g_val被static修饰呢?那么程序将报错无法运行。
因为:全局变量具有外部链接属性,所以可以在其他的源文件内使用。static修饰全局变量时,改变了全局变量的链接属性,由外部链接属性变成了内部链接属性。所谓内部链接属性,就是只能在自己内部源文件能看到。这个静态变量只能在自己的源文件内部使用,不能在其他的源文件内使用了 。
1.1.3 static修饰函数
test1.c
int Add(int x, int y) { return x + y; }
test2.c
extern int Add(int x, int y); int main() { int a = 10; int b = 10; int sum = Add(a, b); printf("%d", sum);//打印20 return 0; }
若Add函数被static修饰,那么该函数只能在该源文件内部使用。
因为:static修饰函数,函数本身也具有外部链接属性,被static修饰之后,就变成了内部链接属性。使得函数只能在自己所在的源文件内部使用,不能在其他文件内使用。
1.2.struct关键字
当我们想要用C语言描述一个学生时,单靠C语言本身的数据类型很难做到。C语言本身提供的类型有点单一,所以这时就可以自定义一个类型,可以用struct关键字来定义。例如:
struct Student { char name[10]; int age; char sex[5]; };
此时我们就创建了一个结构体类型,结构体中的name,age,sex称之为结构体成员。struct Student称为结构体类型,这个struct Student 是自定义类型,int是C语言自带的类型,他们的用法类似,都能创建变量。接下来我们利用struct Student 创建一个结构体变量。如下:
int main() { struct Student student = { "张山",19,"男" }; struct Student student1 = { "李四",17,"男" }; return 0; }
此时我们创建了学生student和student1,我们可以这样理解,struct Student就相当于是个房间设计图纸,通过这个图纸我们设计出很多房间。在创建结构体的同时,我们对结构体成员进行初始化。注意:初始化时要和结构体的成员相对应,例如:张山对应char name[10]; 19对应int age;男对应char sex[5];
那么我们如何访问这些结构体成员呢?这里就要提到之前的 . (结构体成员访问符),下面我们使用这个操作符打印一下结构体成员:
struct Student { char name[10]; int age; char sex[5]; }; int main() { struct Student student = { "张山",19,"男" }; struct Student student1 = { "李四",17,"男" }; printf("%s %d %s", student.name, student.age, student.sex);//打印结果: 张山 19 男 return 0; }
1.3 typedef关键字
定义:typedef是类型重定义符号。可以为一个类型重命名。看示例代码:
注意:typedef是类型重定义,例如下边这段代码,被重定义之后的用法和重定义之前的用法是一样的。
unsigned long long a = 10; int main() { unsigned long long b = 20; printf("%d %d", a,b); return 0; }
我们在写这段代码时,会发现unsigned long long这个类型名字很长,为了方便节省时间,我们通常对其进行类型重定义操作。例如:
typedef unsigned long long ull;//被重定义之后,ull也是一个类型名 unsigned long long a = 10;//这里a和b的类型是一样的 int main() { ull b = 20; printf("%d %d", a,b); return 0; }
改成这样就会方便很多。注意:typedef使用完之后要在语句之后加分号。(就像第一行这样,分号不能少)。
1.4 define定义常量和宏
1.4.1 define定义常量
我们看之前的文章举过的一个例子:
//编译失败 报错 int main() { const int a = 10; int arr[a] = { 0 }; return 0; }
这段代码会报错,注意:数组的括号内必须是常量,因为被const修饰的变量本质上还是变量。那么这里提到的define关键字就可以定义真正的常量。请看代码:
这里特别注意:c99标准之前,数组的大小不能是变量。但在c99标准中引入了变长数组的概念,这时允许数组的大小是变量,但是不能直接被初始化,但是visual studio编译器不支持变长数组。但是有些编译器是支持的,例如:gcc编译器就支持c99中的变长数组。
注意:使用define时,前面的#不要忘了哟
//成功运行 #define MAX 100 int main() { int arr[MAX] = { 0 }; return 0; }
这里的由#define定义的就是常量。#define不仅仅能定义数字,例如:
//成功运行 #define NAME "张山" //define定义字符串 int main() { printf("%s", NAME);//注意以%s格式打印字符串 return 0; }
当然还能定义其他的类型,这里就不赘述了。
1.4.2 define定义宏
回忆一下之前讲过的函数部分,我们提到了Add函数,能完成两个数的相加,下面我们就用宏来实现类似的相乘的功能。
//语法格式 #define 宏名(宏参数)宏体 #define ADD(x,y) ((x)*(y)) int main() { int a = 10; int b = 20; int sum = ADD(a+b, b); printf("%d", sum); return 0; }
可以自己感受一下宏的用法,这里建议((x)*(y))这里分别给x和y的括号不要省略了,假若我们去掉,测试一下:
#define ADD(x,y) (x*y) int main() { int a = 10; int b = 20; int sum = ADD(a+b, b); //编译的时候会替换成: int sum = (a+b*b) 不是我们想要的运算顺序和运算结果 printf("%d", sum); return 0; }
我们会发现,这里就得不到我们想要的结果了,因为宏只是简单字面替换。