c

基础概念

stack

内存中的一个特殊区域,用于存储由每个函数(包括main()函数)参数传递的本地数据,返回地址

堆栈是一个 LIFO(后进先出)数据结构,该结构由CPU非常紧密地管理和优化。每当函数声明一个新变量时,它都会push到堆栈中。 然后,当一个函数退出时,它的所有变量都会从堆栈中弹出(也就是说,它们将被删除)。释放堆栈变量后,该内存区域可用于其他堆栈变量。

无需亲自管理内存,变量会自动分配和释放,堆栈有大小限制(os分配),堆栈变量仅在创建它们的函数运行时存在, 快速访问(堆栈速度更快,因为所有可用内存始终是连续的。不需要维护所有可用内存段的列表,只需一个指向堆栈当前顶部的指针即可。)

CPU有效管理空间,内存不会碎片化,堆栈指针指向堆栈的顶部(堆栈中的最高地址)由高到低地址,堆栈增长到底部

由于堆栈是有限的内存块,调用过多的嵌套函数、无限递归或为局部变量分配过多的空间而导致堆栈溢出

img

heap

主动分配和释放变量,有很多分配和释放时,可能会产生碎片。变量可以全局访问,内存大小无限制(物理限制)。堆内存的读取和写入速度稍慢,因为必须使用指针来访问堆上的内存

随着堆的增长,通常将新块从低地址分配到高地址。随着分配的内存,存储块的大小会增加。

分配大块的请求可能会失败,因为即使空闲块的组合大小可能足够大,也没有一个空闲块足以满足分配请求。这称为堆碎片。 当与空闲块相邻的已用块被释放时,新的空闲块可以与相邻的空闲块合并以创建更大的空闲块,从而有效地减少了堆的碎片。

全局(静态)存储区

分为DATA段和BSS段。

  • DATA段(全局初始化区)存放初始化的全局变量和静态变量

  • BSS段(全局未初始化区)存放未初始化的全局变量和静态变量。

程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。

程序主要包括以下部分

  • 预处理器指令
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
1
2
gcc test1.c test2.c -o main.out
./main.out

gcc 命令如果不指定目标文件名时默认生成的可执行文件名为 a.out(linux)a.exe(windows)

可用 gcc [源文件名] -o [目标文件名] 来指定目标文件路径及文件名。

标识符

C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。

C 标识符内不允许出现标点字符,比如 @、$ 和 %。C 是区分大小写的编程语言。

变量

1
2
3
4
5
6
7
8
9
//声明,不是定义  不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它 可以在别的文件中定义的
extern int i; // 全局变量

int i; //声明,也是定义  建立存储空间的

extern int d = 3, f = 5;    // d 和 f 的声明与初始化
int d = 3, f = 5;           // 定义并初始化 d 和 f
byte z = 22;                // 定义并初始化 z
char x = 'x';               // 变量 x 的值为 'x'

声明之后你不能直接使用这个变量,一个变量一定要先初始化才可以使用,因为 c 语言中默认一个没有初始化的变量值是一个不可知的很大值。定义只能出现在一处。变量越先定义,内存地址就越大。

常量

1
2
3
4
5
6
7
85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

有两种简单的定义常量的方式:

  • 使用 #define 预处理器。 宏替换只作替换,不做计算,不做表达式求解

  • 使用 const 关键字。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'
 
int main()
{
 	 const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
  
   int area;  
  
   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);
 
   return 0;
}

image

static

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。

修饰局部变量可以在函数调用之间保持局部变量的值

修饰符全局变量会使变量的作用域限制在声明它的文件内。全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。

只会被初始化一次,不会在每次调用时重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
 
/* 函数声明 */
void func1(void);
 
static int count=10;        /* 全局变量 - static 是默认的 */
 
int main()
{
  while (count--) {
      func1();
  }
  return 0;
}
 
void func1(void)
{
/* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
 * 每次调用函数 'func1' 'thingy' 值不会被重置。
 */                
  static int thingy=5;
  thingy++;
  printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}

thingy  6  count  9
 thingy  7  count  8
 thingy  8  count  7
 thingy  9  count  6
 thingy  10  count  5
 thingy  11  count  4
 thingy  12  count  3
 thingy  13  count  2
 thingy  14  count  1
 thingy  15  count  0

extern

存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。

extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数

结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
switch(expression){
    case constant-expression  :
       statement(s);
       break; /* 可选的 */
    case constant-expression  :
       statement(s);
       break; /* 可选的 */
  
    /* 您可以有任意数量的 case 语句 */
    default : /* 可选的 */
       statement(s);
}

if(boolean_expression)
{
   /* 如果布尔表达式为真将执行的语句 */
}
else
{
   /* 如果布尔表达式为假将执行的语句 */
}

while(condition)
{
   statement(s);
}

/* for 循环执行 */
for( int a = 10; a < 20; a = a + 1 )
{
  printf("a 的值: %d\n", a);
}

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int max(int, int);

/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

/* 调用函数来获取最大值 */
ret = max(a, b);

数组

1
2
3
double balance[10];
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

指针

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
 // 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}


int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("Address of var variable: %p\n", &var  );
 
   /* 在指针变量中存储的地址 */
   printf("Address stored in ip variable: %p\n", ip );
 
   /* 使用指针访问值 */
   printf("Value of *ip variable: %d\n", *ip );
 
   return 0;
}

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

struct SIMPLE
{
    int a;
    char b;
    double c;
};

struct SIMPLE t1, t2[20], *t3;

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
 

头文件

一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif
  
  
#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

image-20200531110745846

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)
{
 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;
}
 
int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}