先举三反一,再举一反三,学习就应该是这样,先模仿,再改进,最后实现自己的创意! –小甲鱼论坛

递归

  • 调用函数本身
  • 设置递归结束条件
 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
36
37
38
39
40
//
// Created by Luenci on 2019/10/11.
//
#include <stdio.h>

void fun(void);
long fact(int num);

void fun(void){
    static int count = 10;
    printf("Hi\n");
    if (count--){
        fun();
    }
}

long fact(int num){
    long result;

    if(num > 0){
        result = num * fact(num-1);
    } else{
        result = 1;
    }

    return result;
}

int main(void){
//    fun();
    int num;
    long re;
    printf("请输入您要求阶乘的数:");
    scanf("%d", &num);

    re = fact(num);

    printf("%d", re);
    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
#include<stdio.h>

void hanoi(int n, char x, char y, char z);

void hanoi(int n, char x, char y, char z){
    if(n==1){
        printf("%c --> %c \n", x,z);
    } else{
        // 将前n-1个圆盘借助z移动到y
        hanoi(n-1, x, z, y);
        printf("%c --> %c \n", x,z);
        // 将前n-1个圆盘借助x移动到z
        hanoi(n-1, y, x, z);
    }
}

int main(void){
    int n;
    printf("请输入您的层数:");
    scanf("%d", &n);
    hanoi(n, 'X', 'Y', 'Z');

    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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//
// Created by Luenci on 2019/10/11.
//
#include <stdio.h>

void quick_sort(int array[], int left, int right);

void quick_sort(int array[], int left, int right) {
    int i = left, j = right;
    int temp;   // 交换的中间变量
    int pivot; // 基准点

    pivot = array[(left + right) / 2];

    while (i <= j) {
        // 从右往左找到大于等于基准点的元素
        while (array[i] < pivot) {
            i++;
        }
        // 从右到左找到小于等于基准点的元素
        while (array[j] > pivot) {
            j--;
        }
        // 如果 i <= j,则互换
        if (i <= j) {
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
            i++;
            j++;
        }
    }
    if (left < j) {
        quick_sort(array, left, j);
    }
    if (i < right) {
        quick_sort(array, i, right);
    }
}

int main(void){
    int array[] = {73, 108, 111, 101, 78, 109, 66, 48, 88, 135};
    int i, length;

    // 计算数组的长度
    length = sizeof(array) / sizeof(array[0]);

    quick_sort(array, 0, length-1);

    printf("排序后的结果是:");
    for (i = 0; i < length; i++){
        printf("%d ,", array[i]);
    }
    putchar('\n');

    return 0;
}

动态内存管理

  • malloc
    • 申请动态内存空间
  • free
    • 释放动态内存空间
  • calloc
    • 申请并初始化一系列内存空间
  • realloc
    • 重新分配内训空间

malloc函数

  • 函数原型

    • void *malloc(size_t size)
  • malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的地址。

  • 如果函数调用成功,返回一个指向申请的内存空间的指针,由于返回类型是void 指针(void * ), 所以它是可以被转换成任何类型的数据;如果函数调用失败,返回值是NULL。另外,如果size的参数设置为0,返回值也可能是NULL,但这并不意味着函数调用失败。

free函数

  • 函数原型
    • void free(void *ptr)
  • free函数释放ptr参数指向的内存空间。该内存空间必须是有malloccallocrealloc函数申请的。否则,该函数将导致未定义行为。如果ptr参数是NULL,则不执行任何操作。注意:该函数并不会修改ptr参数的值,所以调用后仍然指向原来的地方(变为非法空间)。
 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
//
// Created by Luenci on 2019/10/12.
//

#include <stdio.h>
#include <stdlib.h>

int main(void){
    int *ptr;

    ptr = (int *)malloc(sizeof(int));

    if(ptr == NULL){
        printf("分配内存失败");
        exit(1);
    }

    printf("请输入一个整数:");
    scanf("%d", ptr);

    printf("您输入的整数是:%d\n", *ptr);
    free(ptr);

    printf("您输入的整数是:%d\n", *ptr);

    return 0;
}

内存泄漏

  • 隐式内存泄漏
    • 用完内存块没有及时使用free函数释放
  • 丢失内存块的地址

初始化内存空间

  • mem开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中:
    • memset – 使用一个常量字节填充空间
    • memcpy – 拷贝内存空间
    • memmove – 拷贝内存空间
    • memcmp – 比较内存空间
    • memchr – 在内存空间中搜索一个字符
 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
36
37
38
39
40
41
42
43
44
45
//
// Created by Luenci on 2019/10/12.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N 10

int main(void){

    int *ptr = NULL;
    int  i;

    ptr = (int *)malloc(N * sizeof(int));
    if(ptr == NULL){
        exit(1);
    }

    memset(ptr, 0, N * sizeof(int));

    for (i= 0; i < N; i++) {
        printf("%d ", ptr[i]);
    }
//    putchar("\n");
    putchar('\n');

    free(ptr);

    return 0;
}
/*
1、含义不同。

用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。而一般我们的编译器采用的都是ASCII字符集。因此's'的含义其实和十进制数115的含义是一致的。

而用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针。

2、大小不同。

用单引号引起的一个字符大小就是一个字节。

而用双引号引起的字符串大小是字符的总大小+1,因为用双引号引起的字符串会在字符串末尾添加一个二进制为0的字符'\0'。
*/

calloc函数

  • 函数原型
    • void *calloc(size_t nmemb, sizet_t size);
  • calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb * size),这些内存空间全部被初始化为 0.
  • calloc函数与malloc函数的一个重要区别是:
    • calloc函数在申请完内存后,自动初始化该内存空间为零
    • malloc函数不进行初始化操作,里面数据是随机的
1
2
3
4
5
6
7
8
// 两者等价

// calloc() 分配内存空间并初始化
int *ptr = int *calloc8 sizeofint));

//malloc()分配内存空间并用memset()初始化
int *ptr = (int *)malloc(8 * sizeof(int))
memset(prt, 0, 8 * sizeof(int));

realloc函数

  • 如果ptr参数为NULL,那么调用该函数就相当于调用malloc(size)
  • 如果size参数为0,并且ptr参数不为NULL,那么调用该函数就相当于调用free(ptr)
  • 除非ptr参数为NULL,否则ptr的值必须由先前调用malloc,calloc或realloc函数返回。
 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
//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>
#include <stdlib.h>

int main(void){
    int i, num;
    int count = 0;
    int *ptr = NULL;// 注意这里必须初始化为NULL

    do
    {
        printf("请输入一个整数:(输入-1表示结束)");
        scanf("%d", &num);
        count++;

        ptr = (int *)realloc(ptr, count * sizeof(int));
        if(ptr == NULL){
            exit(1);
        }
        ptr[count-1] = num;
    }while(num != -1);

    printf("输入的整数分别是:");
    for (int i = 0; i <count ; i++) {
        printf("%d ", ptr[i]);

    }
    free(ptr);
    return 0;
}

c语言的内存布局

c

cc

代码段

  • 代码段(Text segment)通常是指用来存放在程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。在代码段中,也有可能包含一些只读的常熟变量,例如字符串常量等。

BSS段

  • BSS段(BSS segment/Uninitialized data segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BBS是英文Block Started Symbol的简称,这个区段中的数据在程序运行前将被自动初始化为数字0。

  • 堆是用于存放进程中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。当进程调用malloc等函数分配内存时,新分配的内存就被动态调价到对上;当利用free等函数释放内存时,被释放的内存从堆中被剔除。

  • 栈是函数执行的内存区域,通常和堆共享同一片区域。

堆和栈的区别

  • 申请方式
    • 堆是由程序员手动申请
    • 栈时系统自动分配
  • 释放方式
    • 堆是由程序员手动释放
    • 栈由系统自动释放
  • 生存周期
    • 堆的生存周期由动态申请到程序员主动释放位置,不同函数之间均可自由访问。
    • 栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问。

高级宏定义

  • 本质就是替换

内联函数

  • 在程序函数调用时,直接展开,而不是去查找调用。

  • 内联函数嵌入调用者代码中的操作是一种优化操作,因此只有进行优化编译时才会执行代码嵌入处理。若编译过程中没有使用优化选项**‘-o’**,那么内联函数的代码就不会真正地嵌入到函数调用者代码中,而是只作为普通函数调用来处理。

  • 内联函数虽然节省了函数调用的时间消耗,但由于每一个函数出现的地方都要进行替换,因此增加了代码的编译时间。另外,并不是所有的函数都能变成内联函数

  • 现在的编译器也很智能,就算你不写inline,它也会自动将一些函数优化成内联函数

不带参数的宏定义

  • 为了和普通的变量进行区分,宏的名字通常我们约定是由大写字母组成

  • 宏定义只是简单地进行替换,并且由于预处理是在编译之前进行,而编译工作的任务之一就是语法检查,所以编译器不会对宏定义进行语法检查

  • 宏定义不是说明或语句,在末尾不必加分号

  • 宏定义的作用是从定义的位置开始到整个程序的结束

  • 可以用 #undef 来终止宏定义的作用域

  • 宏定义允许嵌套

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

#define PI 3.14

int main(){
    int r;
    float s;

    printf("请输入圆的半径:");
    scanf("%d", &r);
// #undef PI 终止宏定义
    s = PI * r * r;
    printf("圆的面积是:%.2f\n", s);


}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//
// Created by Luenci on 2019/10/13.
//
// 宏定义的嵌套
#include <stdio.h>

#define R 6371
#define PI 3.14
#define V PI * R * R * R * 4 / 3

int main(){

    printf("地球的体积是:%.2f\n", V);

}

带参数的宏定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//
// Created by Luenci on 2019/10/13.
//
//带参数的宏定义
#include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(){
    int x,y;

    printf("请输入两个数:");
    scanf("%d%d", &x, &y);
    printf("%d是最大的那个数!\n", MAX(x, y));

}

‘#’ 和 ‘##’

  • ‘#’ 和 ‘##‘是两个预处理运算符
  • 在带参数的宏定义中, #运算符后面应该跟着一个参数,预处理器会把这个参数转换为一个字符串。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

#define STR(s) # s

int main(void){
    printf(STR(Hlloc       %s num = %d), STR(Luenci), 520);

    return 0;
}
out :
Hlloc Luenci num = 520
  • ‘##‘运算符被称为记号连接运算符,我们可以使用’##‘运算符连接两个参数。
 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
 //
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

#define TOGETHER(x,y) x ## y

int main(void){
    printf("%d\n", TOGETHER(2,50));

    return 0;
}

//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

#define TOGETHER(x,y) x ## y

int main(void){
    printf("%d\n", TOGETHER(2,50));

    return 0;
}
out:
250

可变参数

  • 之前学习了如何让函数支持可变参数,带参数的宏定义是使用可变参数的:

    • 1
      
      #define SHOWLIST(...)  printf(#__VA_ARGS__)
      
    • 其中**…**表示可变参数,

      1
      
      #__VA_ARGS__
      

      在预处理中被实际的参数集所替代。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

#define SHOWLIST(...) printf(#__VA_ARGS__)

int main(void){
    SHOWLIST(Luenci,5 20, 3.14 \n);

    return 0;
}

out:
Luenci,5 20, 3.14
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

#define SHOWLIST(format, ...) printf(#format, ##__VA_ARGS__)

int main(void){
    SHOWLIST(num = %d\n,520);
    SHOWLIST(hello Luenci\n);

    return 0;
}
out:
num = 520
hello Luenci