全局变量和局部变量
-
局部变量
- 局部变量就是从开始定义到遇见return 或者遇到}为止
- 存储位置在内存的栈区中
- 特点:
- 相同作用域内不可以定义同名变量,
- 不同作用域可以定义同名变量,但是内部变量会覆盖外面定义的变量
-
全局变量
- 全局变量的作用范围就是从定义的那行开始到文件结束
- 存储在静态存储区
- 特点: 多个同名的全局变量指向同一块存储空间
auto 和 register 关键字
- auto 关键字只能用于修饰局部变量的; 默认就是 auto
- register: 告诉编译器,将局部变量存储到cpu寄存器下
- auto 和 register共同点都是修饰局部变量的
- 好处:
- auto : 特点就是告诉编译器,局部变量离开作用域自动释放
- register好处就是访问的速度回更快;
static 关键字
-
static对局部变量的作用
如果利用static修饰局部变量,那么会将局部变量的存储区域从栈去移动到静态区
静态区只有程序结束才会释放
没有用static修饰的变量
#include
void test(); int main() { test(); test(); test(); return 0; } void test(){ int num = 0; num++; printf("num = %i\n", num); // 局部变量的时候输出1 1 1 } - 用static修饰过的
#include
void test(); int main() { test(); test(); test(); return 0; } void test(){ static int num = 0; num++; printf("num = %i\n", num); // 1 2 3 } -
static 对全局变量的作用
- 全局变量默认是所有文件都可以使用
- 但是如果想要让某个函数在一个文件内使用,那么就可以使用static进行修饰
// main.c 下的文件, 引入了a.h这个头文件
#include
#include "a.h"
int main()
{
int res1 = sum(10, 20);
printf("res1 = %i\n", res1); // 如果函数被Static修饰,则外部文件不能访问报错
int res2 = minus(10, 20);
printf("res2 = %i\n", res2);
return 0;
}
// a.c 下的文件
static int sum(int num1, int num2){
return num1 + num2;
}
int minus(int num1, int num2){
return num1 - num2;
}
static 对函数的作用就是上面的代码
默认情况下, 所有的函数都是外部函数(默认有extern, 但是一般都省略不写)
所以 static修饰函数,就是可以定义一个内部使用函数,不会被外界污染;注意点: 如果只有函数声明添加了static 与 extern, 而定义中没有添加static与
extern, 那么 static的作用无效;
extern 关键字
-
extern对局部变量的作用
- extern 用于声明一个变量,声明变量并不会开辟存储空间
- extern 一般用于全局变量,不能用于局部变量
-
extern对全局变量的作用
- 用于声明一个全局变量, 应为全局变量时从定义开始的哪一行才能进行使用
- 那么使用extern关键字声明这个变量,那么在文件中可以放心使用
- 没有使用extern 声明变量
#include
int main() { num = 666; printf("num = %i\n", num); // 错误 全局变量定义在使用之后 return 0; } int num; - 使用extern 声明变量
#include
extern int num; int main() { num = 666; printf("num = %i\n", num); // 666 return 0; } int num;
编译处理过程
- 源文件 --> 预处理 --> 汇编 --> 二进制 --> 和系统的二进制文件进行链接 --> 可执行程序
预处理
- 预处理指令
- 文件包含 include
- 宏定义 #define
- 条件编译 #if #else #endif
宏定义
- 宏定义会在预处理的时候, 用宏定义对应的值来替换宏
不带参数的宏定义
-
格式: #define 标识符 字符串
- 其中的"#"表示这是一条预处理命令.凡是以"#" 开头的均为预处理命令.
- "define"为宏定义命令.
- "标识符"为所定义的宏名.
- "字符串" 可以是常数,表达式,格式串
#define COUNT 5
- 应用场景: 定义一个宏修改URL, 只需要改一个宏定义就好
-
注意点:
- 1.宏名一般用大写字母,以便与变量名区别开来.
- 2.宏定义的后面不要写分号,因为宏定义是单纯的替换,会用宏的值来替换宏的名称
- 3.宏定义分为两种,一种是带参数的宏定义,另一种是不带参数的宏定义
- 定义一个宏时可以引用已经定义的宏名;
#define R 3.0 #define PI 3.14 #define L 2*PI*R #define S PI*R*R
带参数的宏定义
- 格式: #define 宏名(形参表) 字符串
#define SUM(a, b) a+b
- 注意点:
- 宏定义无论有没有参数,都是单纯的替换
#include
int sum(int a, int b){ return a + b; } #define SUM(a,b) a+b int main() { int num1 = 10, num2 = 20; // int res = SUM(num1, num2); // int res = a + b; // int res = num1 + num2; int res = SUM(num1, num2); printf("res = %i\n", res); return 0; } - 宏的值的每个参数都需要加上(),宏的值的结果也需要加上();
- 只有一个参数时
#include
#define SUM(a) a*a // 写法有点问题 int main() { int num = 5; int res = SUM(num); // 虽然正确输出 printf("res = %i\n", res); return 0; } - 如果宏定义的时候不用小括号括住引发结果错误
#include
#define SUM(a) a*a int main() { // int res = a * a; // int res = 3 + 3 * 3 + 3; int res = SUM(3 + 3); // 15 错误输出 printf("res = %i\n", res); return 0; } - 只给宏的值加上括号还是容易引发输出错误
#include
#define SUM(a) (a)+(a) int main() { // int res = (a) * (a); //int res = (3 + 3) + (3 + 3) / (2 + 2) + (2 + 2); int res = SUM(3 + 3) / SUM(2 + 2); // 输出11 printf("res = %i\n", res); return 0; } - 正确写法: 宏的值的每个参数都需要加上(),宏的值的结果也需要加上()
#include
#define SUM(a) ((a)+(a)) int main() { // int res = ((a) * (a)); //int res = ((3 + 3) + (3 + 3)) / ((2 + 2) + (2 + 2)); int res = SUM(3 + 3) / SUM(2 + 2); // 输出1 // 宏的值的每个参数都需要加上(),宏的值的结果也需要加上() printf("res = %i\n", res); return 0; }
- 在企业开发中使用函数还是使用宏定义
- 如果函数特别简单,一句话能搞定,那么可以使用宏定义,宏定义的效率更高
- 宏定义在预处理时候就执行了,而函数是在运行的时候才会执行
- 宏定义只会做简单的替换,而函数还需要分配内存空间
宏定义的作用域
- 宏定义的作用域和全局变量很像, 但是宏定义可以提前结束
// 宏定义
#define COUNT 666
// 用来提前结束宏定义
#undef COUNT
- 作用域是从定义的那一行开始, 直到文件末尾;
- 可以通过 #undef 宏名 来提前结束宏的作用域
条件编译
- 条件编译和if else语句很像,但是也有区别
- 区别:
- 1.if else语句是在程序运行的时候执行的, 而#if #else #endif 是在预处理的时候执行的
- 2.if else语句所有的代码都会编译到程序中, 而#if #else #endif中满足条件的语句才会编译到程序中
-
if 和 #else的三种写法 和 if else写法类似
- 第一种:
#if 0 == score printf("0分\n"); #endif
- 第二种:
#if 0 == socre printf("0分\n"); #else printf("100分"); #endif
- 第三种:
#define SCORE 100 #if 100 == SCORE printf("100分\n"); #elif 60 <= SCORE printf("及格了\n"); #else printf("不及格\n"); #endif
- 注意点:
- 条件编译中不能获取变量的值
- 因为变量时程序执行的时候才有的,而条件编译时预处理的时候就执行了
- 君生我未生 我生君已老
- 条件编译一般是配合宏定义来使用, 因为宏定义和条件编译都是在预处理的时候执行的
- 应用的小例子
#define DEBUG 0
#if DEBUG == 1
#define URL "127.0.0.1"
#else
#define URL "www.it666.com"
#endif
// 判断有没有定义名称叫做SUM的宏
#ifndef SUM
// 如果没有定义定义就会进入到这里面,然后定义一个叫做SUM的宏
#define SUM
int sum (int num1, int num2);
#endif
文件包含 #include
-
include <>
<>会先从编译器的环境中查找对应的文件,如果没有再从系统的环境中查找对应的文件
-
include "xxx"
"xxx" 会先从当前项目环境中查找对应的文件,如果没有再从编译器的环境中查找对应的文件,如果还没有再从系统环境中查找对应的文件
-
include作用: 将指定文件中的代码原封不动的拷贝到#include的位置
-
inclued执行时间: 预处理的时候执行的
-
注意点:
- 已知函数的定义不可以重复,但是函数的声明可以重复定义
- 但是如果重复声明函数,或重复导入.h文件会影响编译器的编译效率
- 所以在C语言中有专门解决重复导入的方法
// 判断有没有定义名称叫做SUM的宏 #ifndef SUM // 如果没有定义定义就会进入到这里面,然后定义一个叫做SUM的宏 #define SUM int sum (int num1, int num2); #endif
- 特别注意:不要出现循环包含, 例如A的头文件包含B文件,B文件又包含A文件
typedef 关键字
- C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取 "别名";
- 注意点:typedef相当于给人起了一个外号;所以typedef不是定义一个新的数据类型,而是定义一个新的名称
- 格式: typedef 原类型名 新类型名;
- 其中原类型名中含有定义部分,新类型名一般用大写表示,以便区别.
typedef int INTEGER
INTEGER a; // 等价于int a;
- 1.给普通指针起别名, 简化,更容易理解
typedef char* String;
String name = "cww";
printf("name = %s\n", name);
- 2.给结构体起别名
- 2.1先定义结构体类型,再给结构体类型起别名
struct Person{ char *name; int age; }; typedef struct Person ps; ps p = {"cww", 18}; printf("name = %s\n", p.name); // cww
- 2.2定义结构体类型的同时, 给结构体类型起别名
typedef struct Person{ char *name; int age; } ps; ps p = {"cww", 18}; printf("name = %s\n", p.name); // cww
- 2.3定义结构体类型的同时,给结构体类型起别名,并且省略原有类型的名称
typedef struct{ char *name; int age; } Person; // struct Person p = {"cww", 18}; Person p = {"cww", 18}; printf("name = %s\n", p.name);
- 给共用体\枚举起别名,格式和结构体一模一样
- 3.给指向函数的指针起别名
- 如果给指向函数的指针起别名, 那么指针名称就是别名
// 常规写法
int (*funcP)(int , int);
funcP = ∑
int (*funcP2)(int , int);
funcP2 = −
// 注意点:如果给指向函数的指针起别名, 那么指针名称就是别名
typedef int (*funcP)(int , int);
funcP p1 = ∑
funcP p2 = −
int sum(int num1, int num2){
return num1 + num2;
}
int minus(int num1, int num2){
return num1 - num2;
}
- typedef和#define 注意点: 如果要给数据类型起别名, 那么一律用typedef即可;
const 关键字
- const是一个类型修饰符
- 使用const修饰变量则可以让变量的值不能改变
- 注意点:
const可以写在数据类型的前面,也可以写在数据类型的后面;
对于基本数据类型来说,写在数据类型的前面和后面没有任何区别
#include
int main() { // int num = 10; // 不加const, num是一个变量,可以被修改 // 只要在变量前面加上const, 那么这个变量保存的数据就不会修改了 // const int num = 10; int const num = 10; printf("num = %i\n", num); num = 666; // 变量前面加上const ,则变量的值不会修改, 报错 printf("num = %i\n", num); return 0; } - 但是对于指针来说, 写在数据类型的前面和后面就有区别了
const 和指针
- const 如果写在指针数据类型左边和右边(即const写在星号的左边的), 那么代表指针指向的内存空间不能修改, 但是指针的指向可以修改;
#include
int main()
{
// 指针和const
int num = 666;
// 定义一个指针
// int *p;
// const int *p; // const 写在数据类型的左边
int const *p; // const 写在数据类型的右边
p = #
printf("*p = %i\n", *p);
// *p = 888; // const 修饰的变量 不可以修改内存空间的值
printf("*p = %i\n", *p);
int value = 777;
p = &value; // 重新给p赋予value的地址
printf("*p = %i\n", *p); // 输出777
return 0;
}
- const 如果写在指针星号的右边, 那么代表指针指向的内存空间可以修改,但是指针的指向不可以修改;
#include
int main()
{
// 指针和const
int num = 666;
int * const p = #
// p = #
printf("*p = %i\n", *p); // 666
*p = 888;
printf("*p = %i\n", *p); // 888
int value = 777;
// p = &value; // 报错, const 写在星号的右边,则指针的指向不可以修改
printf("*p = %i\n", *p);
return 0;
}