一.#include用法
#include
叫做文件包含命令,用来引入对应的头文件(.h
文件)。#include 也是C语言预处理命令的一种。
include
的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
include 的用法有两种,如下所示:
#include <stdHeader.h>
#include "myHeader.h"
使用尖括号< >
和双引号" "
的区别在于头文件的搜索路径不同:
- 使用尖括号
< >
,编译器会到系统路径下查找头文件; - 而使用双引号
" "
,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。
前面我们一直使用尖括号来引入标准头文件,现在我们也可以使用双引号了,如下所示:
#include "stdio.h"
#include "stdlib.h"
stdio.h 和 stdlib.h 都是标准头文件,它们存放于系统路径下,所以使用尖括号和双引号都能够成功引入;而我们自己编写的头文件,一般存放于当前项目的路径下,所以不能使用尖括号,只能使用双引号。
当然,你也可以把当前项目所在的目录添加到系统路径,这样就可以使用尖括号了,但是一般没人这么做,纯粹多此一举,费力不讨好。
在以后的编程中,大家既可以使用尖括号来引入标准头文件,也可以使用双引号来引入标准头文件;不过,我个人的习惯是使用尖括号来引入标准头文件,使用双引号来引入自定义头文件(自己编写的头文件),这样一眼就能看出头文件的区别。
关于 #include 用法的注意事项:
- 一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令。
- 同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制
- 文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
#include 用法举例
我们早就学会使用 #include 引入标准头文件了,但是如何使用 #include 引入自定义的头文件呢?下面我们就通过一个例子来简单地演示一下。
本例中需要创建三个文件,分别是 main.c、my.c 和 my.h,如下图所示:
my.c 所包含的代码:
//计算从m加到n的和
int sum(int m, int n) {
int i, sum = 0;
for (i = m; i <= n; i++) {
sum += i;
}
return sum;
}
my.h 所包含的代码:
//声明函数
int sum(int m, int n);
main.c 所包含的代码:
#include <stdio.h>
#include "my.h"
int main() {
printf("%d\n", sum(1, 100));
return 0;
}
我们在 my.c 中定义了 sum() 函数,在 my.h 中声明了 sum() 函数,这可能与很多初学者的认知发生了冲突:函数不是在头文件中定义的吗?为什么头文件中只有声明?
「在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。
二.内存分区
c语言有五大内存分区,分别是栈,堆,全局区,常量区,代码区。
栈:在需要的时候由编译器(系统)自动分配,在不需要的时候会由系统自动回收的存储区,内存由系统管理,函数中定义的变量存储在栈中,当调用函数的时候函数中定义的变量会被加到栈中,当函数离开的时候,被添加的变量会从栈中移除,栈在最高的地址上,所以添加的变量地址会逐渐变小,里面的内容可读可写。
堆:是一块动态内存,由程序猿自己申请自己释放的内存空间,其内存由程序猿自己管理,当程序运行时候动态分配给变量,它可以长时间存在,所以,申请一个堆内存就必须有释放的过程,同时指向堆内存的指针不能改变,否则这个堆内存永远都得不到释放,除非程序崩溃或者运行结束导致系统回收程序所在的内存,这样就很容易造成内存泄漏(是指程序运行期间,程序结束后程序所占用的内存就释放了),它是可读可写的。
全局区(globals):定义在函数外边的全局变量和静态变量就放在这个这里,这里的变量在程序已启动就被创建,你可以自由的更改他们,所以是由系统管理的可读可写的内存。
常量区(constants):常量在系统一运行被创建,常量区的内存是只读的,如常量字符串就放在这个区。你可以读他们,但是不可以修改他们,内存由系统管理。
代码区(code):代码区是只读的,该区域是用来存放程序的代码的,内存由系统管理。
注意:这里的栈不是数据结构中所说的栈
动态存储空间(堆)的使用(了解):
- 它的内存由程序猿管理
- 使用:
- 导入stdlib.h
- int *pi = malloc(sizeof(int) malloc向操作系统申请堆中的一块空间,如果操作系统分配成功,就会返回存储空间的地址,否则返回null
- free(void *p):告诉操作系统这块存储空间不再使用了,并不会立即回收,对空指针无效,即不能多次释放同一个指针指针的堆内存空间
- 有malloc就有free,不然容易造成内存泄漏。
- 不要改变指向堆内存的指针
- 使用场景:面试考数据结构要用到堆,如实现队列
- 多种申请堆内存的函数,如:malloc,calloc
三.constant常量
有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定。例如,用一个变量来表示班级的最大人数,或者表示缓冲区的大小。为了满足这一要求,可以使用const
关键字对变量加以限定:
const int MaxNum = 100; //班级的最大人数
这样 MaxNum 的值就不能被修改了,任何对 MaxNum 赋值的行为都将引发错误:
MaxNum = 90; //错误,试图向 const 变量写入数据
我们经常将 const 变量称为常量(Constant)。创建常量的格式通常为:
const type name = value;
const 和 type 都是用来修饰变量的,它们的位置可以互换,也就是将 type 放在 const 前面:
type const name = value;
但我们通常采用第一种方式,不采用第二种方式。另外建议将常量名的首字母大写,以提醒程序员这是个常量。
由于常量一旦被创建后其值就不能再改变,所以常量必须在定义的同时赋值(初始化),后面的任何赋值行为都将引发错误。一如既往,初始化常量可以使用任意形式的表达式,如下所示:
#include <stdio.h>
int getNum(){
return 100;
}
int main(){
int n = 90;
const int MaxNum1 = getNum(); //运行时初始化
const int MaxNum2 = n; //运行时初始化
const int MaxNum3 = 80; //编译时初始化
printf("%d, %d, %d\n", MaxNum1, MaxNum2, MaxNum3);
return 0;
}
-------------------------------------------------------------------
运行结果:
100, 90, 80
const 和指针
const 也可以和指针变量一起使用,这样可以限制指针变量本身,也可以限制指针指向的数据。const 和指针一起使用会有几种不同的顺序,如下所示:
const int *p1;
int const *p2;
int * const p3;
在最后一种情况下,指针是只读的,也就是 p3 本身的值不能被修改;在前面两种情况下,指针所指向的数据是只读的,也就是 p1、p2 本身的值可以修改(指向不同的数据),但它们指向的数据不能被修改。
当然,指针本身和它指向的数据都有可能是只读的,下面的两种写法能够做到这一点:
const int * const p4;
int const * const p5;
const 和指针结合的写法多少有点让初学者摸不着头脑,大家可以这样来记忆:const 离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据,如果近的和远的都有,那么就同时修饰指针变量以及它指向的数据。
const 和函数形参
在C语言中,单独定义 const 变量没有明显的优势,完全可以使用#define
命令代替。const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,就可以用 const 来限制。
在C语言标准库中,有很多函数的形参都被 const 限制了,下面是部分函数的原型:
size_t strlen ( const char * str );
int strcmp ( const char * str1, const char * str2 );
char * strcat ( char * destination, const char * source );
char * strcpy ( char * destination, const char * source );
int system (const char* command);
int puts ( const char * str );
int printf ( const char * format, ... );