学C 一篇就够了

概览

项目地址

https://github.com/impact-eintr/LinuxC

c的历史

  • 1960 原型A语言->ALGOL语言
  • 1963 CPL语言
  • 1967 BCPL
  • 1970 B语言
  • 1973 C语言

C语言特点

  1. 基础性语言
  2. 语法简洁 紧凑 方便 灵活(得益于指针)
  3. 运算符 数据结构丰富
  4. 结构化 模块化编程
  5. 移植性好 执行效率高
  6. 允许直接对硬件操作

学习建议

  1. 概念的正确性
  2. 动手能力
  3. 主动阅读优秀的程序段
  4. 大量练习,编程是技术不是理论

学习思路

  1. 基本概念
  2. 数据类型 运算符 表达式
  3. 输入输出
  4. 流程控制
  5. 数组
  6. 指针
  7. 函数
  8. 构造类型
  9. 动态内存管理
  10. 常用库函数
  11. 调试工具和调试技巧

环境搭建与"Hello world"

环境

  • 当前测试环境是安装了基于archlinuxmanjarolinux发行版的物理机,大家自己搭建linux环境的话推荐试用或租用云服务器或者尝试WSL
  • gcc版本是 10.2.0
  • 编辑器使用vim(推荐vim配置vimplus)

“Hello world”

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

int main(void){
    printf("hello world\n");
    exit(0);
}

gcc 编译c的源文件过程

gcc -v

C源文件->预处理->编译->汇编->链接->可执行文件

完整过程

  • 预处理
gcc -E hello.c > hello.i
  • 编译
gcc -S hello.i 
  • 汇编
gcc -c hello.s 
  • 链接->可执行文件
gcc hello.o -o hello

或者

gcc hello.c -o hello

又或者

make hello

执行

./hello

hello world

基本概念

怎么写代码

头文件的重要性

在c中,如果没有出现函数原型,就默认函数的返回值是int

#include <stdio.h>

int main()
{
<<<<<<< HEAD
    int *num = malloc(sizeof(int));
    *num = 100;
    printf("%d\n",*num);
=======
    int *num = (int *)malloc(sizeof(int));
>>>>>>> d8201497ccb6ce60411ef4f1d6347921a54e7c22
    return 0;
}
hello.c: 在函数‘main’中:
hello.c:5:23: 警告:隐式声明函数‘malloc’ [-Wimplicit-function-declaration]
    5 |     int *num = (int *)malloc(sizeof(int));
          |                       ^~~~~~
          hello.c:5:23: 警告:隐式声明与内建函数‘malloc’不兼容
  • 正确写法
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *num = (int *)malloc(sizeof(int));
    return 0;
}

数据类型 运算符 表达式

  • 基本类型
    • 数值类型
      • short
      • int
      • long
      • float
      • double
    • 字符类型
  • 构造类型
    • 数组
    • 结构体 struct
    • 共用体 union
    • 枚举类型 enum
  • 指针类型
  • 空类型 void
254 -> unsigned int -> 32bit
(254)10 = (1111 1110)2 = (376)8 = (FE)16

254
B11111110(c不认识这个表示)
0376
0xFE
类型转换
int i;
float f;
doubel d;
char ch;

ch + i -> i
f -d -> d

(ch + i) - (dloat -double) -> double

bool
#incldue <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

int main() {
  bool a = false;
  printf("a = %d\n", a);
  exit(0);
}
浮点型的失真问题
int func(float f) {
  if (f < 0) {
    return -1;
  } else if (fabs(f-0) <= 1e-6) {
    return 0;
  } else {
  return 1;
  }
}
char

在iso c中 char有无符号是未定义行为

0
0(整形) '0'(字符常量) "0"(字符串常量) '\0'(字符常量)
输入输出

数据类型要和后续代码中所使用的输入输出要相匹配(小心自相矛盾)

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

int main() {
  unsigned int a;
  a = 1 << 31;
  printf("%d", a);
}

正确

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

int main() {
  unsigned int a;
  a = 1 << 31;
  printf("%ud", a);
}

常量与变量

常量
  • 整形常量 1 890
  • 实型常量 1.2 3.14
  • 字符常量 ‘\t’ ‘\n’ ‘\0’ ‘\015’(8进制) ‘\x7f’ ‘\018’(错误的表示!!)
  • 字符串常量 “” “a” “abXYZ” “abc\n\021\010”(a b c \n \021 \0 1 8)
  • 标识常量
宏的用法
#include <stdlib.h>
#include <stdio.h>

#define PI 3.1415926
#define ADD 2+3
// 正确写法
//#define ADD (2+3)
int main() {
  printf("%f\n", PI);
  printf("%d\n", ADD * ADD);
}

# 6 "pi.c"
int main() {
  printf("%f\n", 3.1415926);
  printf("%d\n", 2+3 * 2+3);
}

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

#define MAX(a,b) ({int A=a,B=b; ((A) > (B) ? (A) : (B));})

int main() {
  int a = 3, b = 5;
  printf("%d\n",MAX(a++, b++));
  printf("%d\n",MAX(a++, b++));
}

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

#define MAX(a,b) ({typeof(a) A=a,B=b; ((A) > (B) ? (A) : (B));})

int main() {
  int a = 3, b = 5;
  printf("%d\n",MAX(a++, b++));
  printf("%d\n",MAX(a++, b++));
}

在程序的预处理阶段,占编译时间,不占运行时间(没有函数调用的消耗),但不检查语法(比较危险)

变量

[存储类型] 数据类型 标识符 = 值
TYPE NAME = VALUE

  • auto 默认 自动分配空间

  • register 建议型 寄存器类型 只能定义局部变量,不能定义全局变量,大小有限制,只能定义32位大小的数据类型,比如double就不可以。因为寄存器没有地址,所以一个register类型的变量无法打印出地址查看或使用。

  • static 静态型 自动初始化为0值或空值 并且static变量的值有继承性。另外常用来修饰一个变量或者函数(防止当前函数对外扩展)

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

void func() {
  static int x = 1;
  x++;
  printf("%d\n", x);
}

int main() {
  func();
  func();
  func();
}

2
3
4
  • extern 说明型 意味着不能改变被说明的量的值或类型 可以用来扩展外部变量的作用域
#ifndef EXTERN_H__
#define EXTERN_H__

void func();

#endif
#include "extern.h"

extern int i; // 不定义 而是引用了其他地方的i
int func() {
  printf("[%s]%d\n", __FUNCTION__, i);
}
#include "stdlib.h"
#include "stdio.h"
#include "extern.h"

int i = 10;

int main() {
  printf("[%s]%d\n", __FUNCTION__, i);
}

表达式

逻辑运算符的短路性
#include <stdio.h>
#include <stdlib.h>

int main() {
  int a = 1, b = 2, c = 3, d = 4;
  int m = 1, n = 1;
  
  (m = a > b) && (n = c > d);
  
  printf("m = %d\n n = %d\n", m, n); // m : 0 n : 1
}
sizeof
#include <stdio.h>
#include <stdlib.h>

int main() {
  printf("%lu, %lu, %lu, %lu, %lu, %lu, %lu\n",
         sizeof(int),sizeof(short), sizeof(long),
         sizeof(double), sizeof(float), sizeof(char), sizeof(void*));
}

4, 2, 8, 8, 4, 1, 8

位运算
  • | 按位或
  • & 按位与
  • ^ 按位异或
  • ~ 按位取反

应用

将操作数中的第n位置1 其他位不变 num = num | 1 << n;
将操作数中的第n位置0 其他位不变 num = num & ~(1<<n);
测试第n位: if(num & (1<<n))

IO

  • printf
变长参数
int main() {
  int i = 123;
  printf("%4d\n", i);
  
  float f = 1.23455;
  printf("%.3f\n", f);
  
  char* s= "helloworld";
  printf("%10.5s\n", s);
}
刷新缓冲区
int main() {
  printf("[%s:%d] before while().", __FUNCTION__, __LINE__);
  while(1);
  printf("[%s:%d] after while().", __FUNCTION__, __LINE__);
}

正确写法

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

int main() {
  printf("[%s:%d] before while().]\n", __FUNCTION__, __LINE__);
  // 或者
  //printf("[%s:%d] before while().", __FUNCTION__, __LINE__);
  //fflush(stdout); 
  while(1);
  printf("[%s:%d] after while().", __FUNCTION__, __LINE__);
}

  • scanf
int main() {
  int i;
  scanf("%d", &i);
  printf("%d\n", i);
}

scanf 在使用 %s 的时候要特别小心

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

int main() {
  char S[3];

  scanf("%s", S); // 如果输入 abcdef
  printf("%s", S); // 可能会出现段错误
}

scanf 在循环中使用的时候要特别小心

int main() {
  int ret = 0;
  int d = 0;
  
  while(1) {
    ret = scanf("%d, d);
    if (ret == -1) {
      perror("Error");
      break;
    }
    printf("&d\n", d);
  }
  exit(0);
}

处理换行

int main() {
  int i = 0;
  char c = 0;
  
  scanf("%d", &i);
  scanf("%*c%c", &c);
  // 或者
  //getchar();
  //scanf("%c", &c);
  printf("i = %d, c = %c", i, c);
}

流程控制

跳过

数组

构造类型 连续存放

一维数组

[存储类型] 数据类型 标识符[下标]

初始化

  • static
static int a[10];
  • {}
int a[3] = {1, 2, 3};

元素引用

  • arr[i]
  • arr+i
数组名

一个常量

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

int main() {
  int arr[3] = {1, 2, 3};
  printf("%ld\n", sizeof(arr));
  // 下面这句是错的
  arr = {4, 5, 6};
  for (int i = 0;i < sizeof(arr)/sizeof(int);i++) {
    printf("%d", *(arr+i));
  }
}

数组越界

c对数组不进行越界检查,需要格外小心

练习

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

int main() {
  int fib[10] = {1, 1};

  for (int i = 2;i < 10;i++) {
    fib[i] = fib[i-1]+ fib[i-2];
  }
  for (int i = 0;i < 10;i++) {
    printf("%d ", fib[i]);
  }
}

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

int main() {
  int arr[] = {2, 3, 5, 4, 6, 7, 1, 9};
  for (int i = 0;i < sizeof(arr)/sizeof(int);i++) {
    for (int j = 0;j < sizeof(arr)/sizeof(int)-1-i;j++) {
      if(arr[j] > arr[j+1]) {
        int tmp = arr[j];
        arr[j] = arr[j+1];
        arr[j+1] = tmp;
      }
    }
  }
  for (int i = 0;i < sizeof(arr)/sizeof(int);i++) {
    printf("%d ", arr[i]);
  }
}

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

int main() {
  int arr[] = {3, 2, 5, 4, 9, 7, 1, 6};
  for (int i = 0;i < sizeof(arr)/sizeof(int);i++) {
    int m = i;
    for (int j = i+1;j < sizeof(arr)/sizeof(int);j++) {
      if(arr[j] < arr[m]) {
        m = j;
      }
    }
    if (m != i) {
      int tmp = arr[i];
      arr[i] = arr[m];
      arr[m] = tmp;
    }
  }
  for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
    printf("%d ", arr[i]);
  }
}

二维数组

[存储类型] 数据类型 标识符[行下标][列下标]

int main() {
  int a[M][N] = {1, 2, 3, 4, 5};
  int b[][N] = {1, 2, 3, 4, 5};
  int c[M][] = {1, 2, 3, 4, 5}; // 错误
  for (int i = 0;i < M;i++) {
    for (int j = 0;j < N;j++) {
      printf("%d ", *(a+i+j*));
    }
  }
}
深入理解二维数组

a[2][3] = b[3] + c[3]
a[0] = b[0]
a[1] = c[0]

字符数组

定义以及初始化

[存储类型] char 标识符[]

注意部分初始化的时候,剩余部分会自动初始化为’\0’

IO

scanf 无法获取带有分隔符的字符串(\t, \n, )

常用函数

  • strlen & sizeof
  • strcpy & strncpy
  • strcat & strncat
  • strcmp & strncmp

单词统计

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

#define STRSIZE 1024

int main() {
  char str[STRSIZE] = {};
  fgets(str, STRSIZE, stdin);
  int count= 0, flag = 0;

  for (int i = 0;str[i] != '\0';i++){
    if (str[i] == ' ') {
      flag = 0;
    } else if(!flag) {
      count++;
      flag = 1;
    }
  }
  printf("%d\n", count);
}

指针

64位环境 指针类型占用8个字节
32位环境 指针类型占用4个字节

变量与地址

变量对某块内存的抽象表示
指针 == 地址 变量名 == 抽象出来的某块空间的别名

指针与指针变量

int i = 1;
int *p = &i;
int ** q = &p;


直接访问与间接访问

i = 1;
&i = 0x2000;
p = 0x2000;
&p = 0x3000;
*p = 1;
q = 0x3000;
&q = 0x4000;
*q = 0x2000;
**q = 1;

空指针与野指针

空类型

char *s = "hello";
void *i = s;

定义与初始化的写法

指针运算

& * 关系运算 ++ –

指针与数组

指针与一维数组

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

int main () {
  int a[3] = {1, 2, 3};
  // a是常量 p是变量
  int *p = a;

  for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
    printf("%p -> %d\n", p+i, *(p+i));
  }
}

p++ != p+1

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

int main () {
  int a[3];
  int *p = a;

  for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
    scanf("%d", p++);
  }

  for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
    printf("%d\n", *(p++));
  }
}

指针与二维数组

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

int main() {
  int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
  int (*p)[3] = a;

  for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
    for (int j = 0;j < sizeof(*a)/sizeof(**a);j++) {
      printf("%d ", *(*(p+i)+j));
    }
  }
}

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

int main() {
  int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
  int *p = &a[0][0];

  for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
    for (int j = 0;j < sizeof(*a)/sizeof(**a);j++) {
      printf("%d ",*(p+(i * sizeof(*a)/sizeof(**a))+j));
    }
  }
}

const与指针

const float pi = 3.14; // 常量化变量

先看到指针就是指针 先看到常量就是常量

  • 常量指针 指向的内存不能通过这个指针修改
const int* p;

int const *p;

char *strcpy(char *restrict dest, const char *src); // src是源字符串 不应该被修改
  • 指针常量 指向的位置不能变 可以通过这个指针修改内存的值

int *const p;

const int *const p;

指针数组与数组指针

指针数组

int *arr[3]

指针数组排序

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

int main() {
  char *name[5] = {"golang", "java", "c", "dart", "erlang"};

  int k;
  char *tmp;
  for (int i = 0;i < (sizeof(name)/sizeof(*name))-1;i++) {
    k = i;
    for (int j = i+1;j < (sizeof(name)/sizeof(*name));j++) {
      if (strcmp(name[k], name[j]) > 0) {
        k = j;
      }
    }
    if (k != i) {
      tmp = name[i];
      name[i] = name[k];
      name[k] = tmp;
    }
  }

  for (int i = 0;i < (sizeof(name)/sizeof(*name));i++) {
    printf("%s\n", *(name+i));
  }
}

数组指针

int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
int (*p)[3] = a;
#include <stdlib.h>
#include <stdio.h>

int main() {
  int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
  int (*p)[3] = a;

  for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
    for (int j = 0;j < sizeof(*a)/sizeof(**a);j++) {
      printf("%d ", *(*(p+i)+j));
    }
  }
}

多级指针

没啥好说的

函数

函数的定义

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

int main() {
  exit(printf("Hello!\n"));
}

函数的传参

  • 值传递
  • 地址传递
  • 全局变量

函数的调用

  • 嵌套
#include <stdlib.h>
#include <stdio.h>

int max(int a, int b, int c) {
  int tmp = a > b ? a : b;
  return tmp > c ? tmp : c;
}

int min(int a, int b, int c) {
  int tmp = a < b ? a : b;
  return tmp < c ? tmp : c;
}

int dist(int a, int b, int c) {
  return max(a, b, c) - min(a, b, c);
}

int main() {
  printf("%d\n", dist(8, 5, 10));
}

  • 递归
#include <stdio.h>
#include <stdlib.h>

int func(int n) {
  if (n < 0) {
    return -1;
  }
  if (n == 1 || n == 2) {
    return 1;
  }
  return func(n-1) + func(n-2);
}

int main() {
  int n;
  scanf("%d", &n);
  printf("fib %d = %d", n, func(n));
  exit(0);
}

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

int func(int n) {
  if (n < 0) {
    return -1;
  }
  if (n == 0 || n == -1) {
    return 1;
  }
  return n * func(n - 1);
}

int main() {
  int n;
  scanf("%d", &n);
  printf("%d! = %d", n, func(n));
  exit(0);
}

函数与数组

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

// 注意这里的int *arr 的大小是8个字节 是一个普通的指针不是数组 所以一定要传大小
void printarr(int *arr, int size) {
  for (int i = 0;i < size;i++) {
    printf("%d ", *(arr+i));
  }
  printf("\n");
}

int main() {
  int arr[] = {1, 2, 3, 4, 5};
  printarr(arr, sizeof(arr)/sizeof(*arr));
}

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

void printarr(int *arr, int size) {
  for (int i = 0;i < size;i++) {
    printf("%d ", *(arr+i));
  }
  printf("\n");
}

void printarr2(int (*p)[3], int m , int n) {
  for (int i = 0;i < m;i++) {
    for (int j =0;j < n;j++) {
      printf("%4d ", *(*(p+i)+j));
    }
    printf("\n");
  }
}

int main() {
  int arr[][3] = {1, 2, 3, 4, 5, 6};
  printarr2(arr, 2, 3);
}

函数的指针

  • 指针函数
#include <stdio.h>
#include <stdlib.h>

#define M 2
#define N 3

int *findnum(int (*p)[N], int num) {
  if (num > M - 1) {
    return NULL;
  }
  return *(p + num);
}

int main() {
  int arr[M][N] = {{1, 2, 3},{ 4, 5, 6}};

  int *res = findnum(arr, 1);

  for (int i = 0; i < N; i++) {
    printf("%d ", *(res + i));
  }
}

  • 函数指针
    类型 (*指针名)(形参)

  • 函数指针数组
    类型 (*数组名[下标]) (形参)

指向指针函数的函数指针数组

int *(*funcp[N])(int)

实际例子

int pthread_create(pthread_t *restrict thread,
                          const pthread_attr_t *restrict attr,
                          void *(*start_routine)(void *),
                          void *restrict arg);

构造类型

结构体

产生及意义

描述复杂的数据类型

类型描述

struct node_st{
  type1 name1;
  type2 name2;
  ...
};

嵌套定义

struct day {
  int H;
  int M;
  int S;
};

struct student_st{
  char *name;
  day day;
};

定义变量 初始化以及成员引用

  • 结构体 .
  • 结构体指针 ->

struct A {
  int i;
  char c;
  float f;
};

int main() {
  // TYPE NAME = VALUE;
  struct A a = {123, 'A', 2.22}; // 初始化
  struct A a_ = { .c = 'A', .f = 2.22}; // 部分初始化
  struct A *ap = { .c = 'A', .f = 2.22}; // 部分初始化
  
  printf("%d %c %.2f\n",a.i, a.c, a.f); // 成员引用
  // 123 A 2.22
  printf("%d %c %.2f\n",a_.i, a_.c, a_.f); // 成员引用
  // 0 A 2.22
  printf("%d %c %.2f\n",ap->i, ap->c, ap->f); // 成员引用
  // 0 A 2.22
}

占用内存空间大小

addr % sizeof(type) 不能整除的话就要继续往下偏移

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

struct A {
  int i;
  char c;
  float f;
};

// 可以使用下面的方法取消对齐 常用于网络通信
struct B {
  int i;
  char c;
  float f;
}__attribute__((packed));

int main() {
  struct A a;
  struct B b;

  printf("A = %ld\n", sizeof(a));
  printf("B = %ld\n", sizeof(b));
}

共用体

产生及意义

N选一 多个成员共用一块空间 取最大的成员的类型大小作为共用体的类型大小

类型描述

union test_un{
  int i;
  float f;
  double d;
  char ch;
};

嵌套定义

同结构体 可以互相嵌套

定义变量 初始化以及成员引用

成员引用:

  • u.成员名
  • up->成员名

32位的无符号数的高16位和低16位相加

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

int main() {
  uint32_t i = 0x11223344;
  printf("%x\n", (i>>16)+(i&0xFFFF));
}

另一种写法

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

union {
  struct {
    uint16_t i;
    uint16_t j;
  }x;
  uint32_t y;
}u;

int main() {
  uint32_t i = 0x11223344;
  printf("%x\n", (i>>16)+(i&0xFFFF));

  u.y = 0x11223344;
  printf("%x\n", u.x.i + u.x.j);
}

枚举

enum 标识符{
  成员1;
  ...
};
enum dar {
  MON = 1,
  TUS,
  WEB,
  THR,
  FRI,
  SAT,
  SUN,
};

int main() {
  enum day a = FRI;
  
  printf("%d\n", a);
}
enum status {
  RUNNING = 1,
  STOP,
  PAUSE,
};

struct job {
  int id;
  int state;
  time_t start, end;
};

int main() {
  struct job_st job1;
  
  switch(jobs.state) {
    case RUNNING:
      // TODO
      break;
    case STOP:
      // TODO
      break;
    case PAUSE:
      // TODO
      break;
    default:
      // TODO
      abort();
  }
}

typedef

typedef type typename

typedef int INT

int main() {
  INT i = 9;
}

typedef 和 define 的区别

#define IP int *
type int *IP;

int main() {
  // 宏
  IP p, q;
  int *p, q; // 一个int * 一个int
  
  // typedef
  IP p, q;
  int *p, *q; // 两个int *
}

数组

typedef int ARR[6]; // int [6] 改名为 ARR

ARR a; // int a[6];

结构体

typedef struct {
  int i;
  float f;
}NODE, *NODEP;

函数

typedef int *FUNC(int)

函数指针

typedef int* (*FUNCP)(int)

动态内存管理

  • malloc
  • calloc
  • realloc
  • free
    谁申请谁释放
#include <stdlib.h>
#include <stdio.h>

int main() {
  int *ip = malloc(sizeof(int));

  *ip = 1;

  printf("%d\n", *ip);
  free(ip);
}

动态数组

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

int main() {
  int *p;
  int num = 5;
  p = malloc(num * sizeof(int));

  for (int i = 0;i < num;i++) {
    scanf("%d", p+i);
  }
  for (int i = 0;i < num; i++) {
    printf("%d ", *(p+i));
  }

  printf("\n");
  exit(0);
}

内存申请与函数传值

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

void func1(void *p, size_t size) {

  if(p == NULL) {
    return;
  }
  p = malloc(size);
}

void func2(int **p, size_t size) {

  if(*p == NULL) {
    return;
  }
  *p = malloc(size);
}

void *func3(void *p, size_t size) {
  if(p == NULL) {
    return NULL;
  }
  p = malloc(size);
  return p;
}

int main() {
  int num = 100;
  int *p = NULL;

  func1(p, num); // 内存会泄露

  func2(&p, num); // 传递二级指针

  p = func3(p, num); // 将申请的内存返回

  free(p);
  exit(0);
}

free的理解

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

void func2(int **p, size_t size) {

  if(*p == NULL) {
    return;
  }
  *p = malloc(size);
}

int main() {
  int num = 100;
  int *p = NULL;

  func2(&p, num); // 传递二级指针

  free(p);
  // p = NULL;
  
  *p = 123;
  printf("%d\n", *p); // 这个指针已经是野指针了 
  
  exit(0);
}
  • free代表着变量p不再拥有原来指向内存空间的引用权限
  • free后最好马上将指针置NULL

Makefile

工程管理 依赖管理

  • makefile(用户自定义 更高优先级)
  • Makefile(默认)
mytool:main.o tool1.o tool2.o
  gcc main.o tool1.o tool2.o -o mytool

main.o:main.c
  gcc main.c -c -Wall -g -o main.o
tool1.o:tool1.c
  gcc tool1.c -c -Wall -g -o tool1.o
tool2.o:tool2.c
  gcc tool2.c -c -Wall -g -o tool2.o
  
OBJS=main.o tool1.o tool2.o
CC=gcc

mytool:$(OBJS)
  $(CC) $(OBJS) -o mytool

main.o:main.c
  $(CC) main.c -c -Wall -g -o main.o
tool1.o:tool1.c
  $(CC) tool1.c -c -Wall -g -o tool1.o
tool2.o:tool2.c
  $(CC) tool2.c -c -Wall -g -o tool2.o
 
clean:
  $(RM) $(OBJS) mytool -r

$^ 表示在上一句依赖关系中被依赖的所有文件
$@ 表示在上一句依赖关系中依赖项的目标文件

CFLAGS=-Wall -g -c
OBJS=main.o tool1.o tool2.o
CC=gcc

mytool:$(OBJS)
  $(CC) $^ -o $@

main.o:main.c
  $(CC) $^ $(CFLAGS) -o $@
tool1.o:tool1.c
  $(CC) $^ $(CFLAGS) -o $@
tool2.o:tool2.c
  $(CC) $^ $(CFLAGS) -o $@
 
clean:
  $(RM) $(OBJS) mytool -r

% 表示同一个名字

CFLAGS=-Wall -g -c
OBJS=main.o tool1.o tool2.o
CC=gcc

mytool:$(OBJS)
  $(CC) $^ -o $@

%.o:%.c
  $(CC) $^ $(CFLAGS) -o $@
 
clean:
  $(RM) $(OBJS) mytool -r
  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 非常感谢您的建议,我们会尽快为大家准备一篇关于C语言入门的习笔记,帮助大家更好地掌握这门编程语言。我们会尽可能详细地介绍C语言的基本语法、数据类型、运算符、流程控制语句等内容,同时也会提供一些实例代码和练习题,帮助大家更好地理解和掌握C语言编程。希望我们的习笔记能够对大家有所帮助,让大家更快地入门C语言编程。 ### 回答2: C语言是一门广泛应用于系统编程、嵌入式系统、游戏开发等领域的高级程序设计语言。入门C语言对于软件开发工程师和计算机科爱好者来说非常重要。 在C语言习笔记中,我们首先需要熟悉C语言的基础知识,如数据类型、变量、运算符、流程控制语句和函数等。了解基础知识将会帮助我们更好地理解后续习内容。 其次,C语言需要掌握一些编程基本技巧,如程序的模块化设计、调试和测试、程序的优化等。这些技巧可以帮助我们提高代码质量和效率。 同时,我们还需要了解C语言的高级编程特性,如指针、动态内存分配、结构体、文件操作等。掌握这些编程特性可以让我们更加灵活地进行编程。 最后,想要成为一名优秀的C语言程序员,我们需要不断实践和探索,不断完善自己的编程技能、提高编程水平。需要经常练习编写代码,加入开源社区进行开源项目的贡献和参与,与其他程序员交流经验,扩展自己的技术视野。 总之,C语言需要投入大量的时间和精力,需要不断地习、实践和交流。但是,在通过不断地习和实践后,C语言将成为你的强大工具,可以开发出各种高效、可靠的应用程序,实现自己的编程理想和目标。 ### 回答3: C语言是一门非常基础但又非常重要的编程语言,这门语言被广泛应用于各个领域,如嵌入式系统,操作系统开发等。C语言入门,是每个程序员必经的过程,通过C语言,我们掌握了基本的编程思想和方法,同时也为我们日后习其他高级语言奠定了扎实的基础。 在这篇习笔记中,我们可以C语言的各种基础知识点,例如数据类型、运算符、控制语句、函数等。这些知识点是C语言编程的基础,掌握它们非常重要。在习的过程中,需要认真阅读教材,并且要动手实践,自己编写一些小程序,才能真正理解和掌握知识点。 除此之外,我们还可以通过C语言的标准库函数来扩展语言的使用范围,这些标准库函数非常常用,不仅可以方便快捷地实现某些功能,而且也是日后习其他语言时会用到的知识点。 在C语言过程中,需要有一个良好的习态度,要不断地做笔记、做练习,不断地复习、总结,才能够真正掌握这门语言,更好的为日后的编程生涯打好基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值