小破站学习嵌入式c语言笔记

概览

此笔记仅供大家免费学习参考所用

视频地址

https://www.bilibili.com/video/BV18p4y167Md/

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)
  • vim配置脚本以及常用快捷方式

“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 
  • 链接->可执行文件
hello.o//目标文件
gcc hello.o -o hello

或者

gcc hello.c
gcc hello.c -o hello

又或者

make hello

执行

./hello

hello world

基本概念

怎么写代码

头文件的重要性

以函数为单位来进行程序编写

声明部分+实现部分;return 0; 多用空格空行;添加注释;

防止写越界,防止内存泄露,谁打开水关闭,谁申请谁释放;

程序员的约定:在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
    • 字符类型(char)
  • 构造类型
    • 数组
    • 结构体 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);
}
浮点型的失真问题
float类型的数本身精度不精确
int func(float f) {
  if (f < 0) {
    return -1;
  } else if (fabs(f-0) <= 1e-6) {
    return 0;
  } else {
  return 1;
  }
}

浮点数
 
比喻1e1(科学计数法)
 
e后面跟的是10的指数(也就是1的10次方,e表示10次方),f表示浮点数
1e1表示1×10¹,其实就是10
再例如5e2f,表示5×10²,也就是500
 
========================================================================================================================
 
-1.56E+12 的常量表示法怎么计算?
理解为1.56的12次方的负数?也就是:-1560000000000?
 
-1.56*10^12=-1560000000000
理解为-1.56*10的12次方-1560000000000
========================================================================================================================
 
 
   0.1101101111
+  0.0000000001
---------------   
   0.110110000
 
 
因为在任何区间内(比如1.0和2.0之间)都存在无穷多个实数,所以计算机浮点数不能表示区域内所有的值。浮点数往往只是实际值的近似。例如7.0可能以浮点数值6.99999存储。
 
解释
 
十进制转化为二进制的方法是 依次与2^(-n)作比较(n从1开始)
若大于该值则为1,且减去此值,否则为0;然后继续下一轮比较 
 
举例说明:将0.842356转换成二进制,你会发现比较将会是无穷无尽的。
如果你截取到某位,必须做一些取舍。取舍的标准是:其后一位若为1则进1;后一位为0则不进。
若要截取9位,因为第10位为0,故不进位,则最终的结果为:0.110101111;
若要截取到8位,因为第9位为1,故要进位,则最终的结果为:0.110110000(即0.1101101111 + 0.0000000001)。
从这个例子可以看出十进制小数的转换成二进制时只是一个近似值。其实大部分浮点数保存在计算机中都只是一个近似值。至于是稍微大于原值还是稍微小于原值,要看截取时有无进位。
 
 
 
0.842356
 
0.110101111 0 1001001010010010001111100101101110000101011  截取第9位 第10位为0,所以不进位=0.110101111
 
0.11010111  1 01001001010010010001111100101101110000101011  截取第8位 第9位为1,所以进位  =0.110110000
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<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
int main(){
    unsigned a=4146464646;
    printf("a=%d \n",a);
    exit(0);
}
输出:a=-148502650
 4294967295 - 4146464646 = 148502650

正确

#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’ (16进制)‘\018’(错误的表示!!)

  • 字符串常量:由双引号引起来的一个或多个字符组成的序列,如:

    “” “a” “abXYZ” “abc\n\021\010”(a b c \n \021 \0 1 8)

  • 标识常量 :#define,处理在程序的预处理阶段,占编译时间;特点一改全改;缺点:不检查语法,只是单纯的宏体与宏名之间的替换。

宏的用法
#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);
}
#ifndef Tool1_H_
#define Tool1_H_
//如果没有Tool1_H_,那就define一个Tool1_H_
# 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));})
//define MAX(a,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>
//typeof函数获取a的类型
#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 (说明型) 意味着不能改变被说明的量的值或类型 可以用来扩展外部变量的作用域(extern就相当于我虽然不知道在哪里有,但是肯定存在。就用extern)

变量的生命周期和作用范围

​ 1.全局变量和局部变量 2.局部变量和局部变量 3.图片

#ifndef EXTERN_H__
#define EXTERN_H__

void func();

#endif
#include "extern.h"

extern int i; // 不定义 而是引用了其他地方的i
int func() {
  printf("[%s]%d\n", __FUNCTION__, i);/*gcc自带的宏函数,当前所在的函数名*/
}
#include "stdlib.h"
#include "stdio.h"
#include "extern.h"

int i = 10;

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

linux命令
vim * -p   打开当前目录所有文件
gt 或 gT 左右切换    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2TPlmFha-1693146368195)(E:\Git_study\LinuxC\C\image-20230729175127822.png)]

表达式

表达式与语句的区别

运算符部分:1.每个运算所需要的参与运算的操作数个数

2.结合性

3.优先级

4.运算符的特殊用法; 如:% 、==和=、 逻辑运算符(&&,||)的短路特性;

5.位运算的重要意义:将操作数中的第n位置1 其他位不变: num = num | 1 << n;// num | (1 << n)

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

测试第n位: if(num & (1<<n))

逻辑运算符的短路性
#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
}

a -= a *= a += 3;//最终a的值为0;
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;// num | (1 << n)

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

测试第n位: if(num & (1<<n))

从一个指定宽度的数中取出其中的某几位:

比如对于1101, 你要第二位(1)

1101 & 0100 = 0100

0100 >> 2 = 01 = 1

假设 i = B1100 =12

~i --> B0011;

i >> 1 --> 110 =6;

110 << 1 --> 1100 = 12;

 int i = B1100;
 int j = B1001;
 i | j 
   1100
|  1001
---------
   1101 
 
  i & j  
   1100
&  1001
---------
   1000 
     
 i ^ j  
   1100
^  1001
---------
   0101  
 
   if i=B1000  
   int i=i|1<<2
   1000
|   100
---------
   1100      

从一个指定宽度的数中取出其中的某几位:
比如对于1101, 你要中间两位
1101 & 0110 = 0100

0100 >> 1 = 010 = 10   

IO

input & output -> I/O(标准IO,文件IO)

  • 格式化输入输出函数:scanf,printf

    int printf(const char *format, …);

    format:“%[修饰符] 格式字符”

    参照图片标准输出修饰符与输入输出格式字符。

    int scanf(const char *format, …);//省略的是地址表

    format:抑制符*

    scanf 在使用 %s 的时候要特别小心,已经越界却不报错,放在循环结构中要注意能否接收到正常有效的内容。

  • 字符输入输出函数:getchar,putchar

  • 字符串输入输出函数:gets(!),puts

    gets非常危险,不要用,用fgets()或者getline()来代替

image-20230729205527447 image-20230729211629700
变长参数
int main() {
  int i = 123;
  printf("%4d\n", i);
  
  int d=65;
  printf("%c\n",d);\\A
    
  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__);
}
 /n还有刷新缓冲区的意思,
 __LINE__打印该语句行号
  exit()会刷新io流

正确写法

#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); 
  //sleep(5);//睡眠5秒
  while(1);
  printf("[%s:%d] after while().", __FUNCTION__, __LINE__);
}

  • scanf
int main() {
  int i;
  scanf("%d", &i);
  printf("%d\n", i);
}
//空格 回车 tab键

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);//加scanf返回值的校验
    if (ret == -1) {
      perror("Error enter");
      break;
    }
    printf("%d\n", d);
  }
  exit(0);
}

处理换行

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

10是换行符号,32是空格的ASCII码

流程控制

//逗号表达式的值,去最后一个表达式的值。

1.goto语句(慎用)
goto语句是一种无条件转移语句,goto 语句的使用格式为:

goto  语句标号;

其中语句标号是一个有效的标识符,这个标识符加上一个 ":" 一起出现在函数内某处,执行goto语句后,程序将跳转到该标号处并执行其后的语句;

另外语句标号必须与goto语句同处于一个函数中,但可以不在一个循环层中;通常goto语句与if条件语句连用,当满足某一条件时,程序跳到标号处运行;如下例子:

#include <stdio.h>
int main(void){
    int i,sum=0;
    i=1;
    loop: if(i<=100){
        sum=sum+i;
        i++;
        goto loop;
    }
    printf("%d\n",sum);
    return 0;
}
2.if语句
if使用格式为:

if(condition1)
{}
else if(condition2)
{}
else if(condition3)
...
else
{}

除此之外,if语句之间可以嵌套,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g6eXxCz3-1693146368196)(E:\Git_study\LinuxC\C\watermark,type_ZmFuZ3poZ34.jpg)]

 3.switch语句
switch的语法格式为:

switch (表达式)
{
    case 常量1:语句1
    case 常量2:语句2
    ...
    default: 语句n+1
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WK54bbMQ-1693146368196)(E:\Git_study\LinuxC\C\watermark,type_ZmFuZ34.jpg)]

注意:

1)switch后面括号内的表达式,其值类型应该为整数类型,包括字符型;

2)可以没有default语句,此时流程转到switch语句的下一条语句执行;

3)每一个分支语句后一般都要加一条break语句,用来跳出switch语句块,最后一个分支可以不加;如将上面的例子改为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLNSby8N-1693146368196)(E:\Git_study\LinuxC\C\20210816235525146.png)]

此时的执行结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aE4geTmN-1693146368197)(E:\Git_study\LinuxC\C\20210816235600523.png)]

这是因为分支后的常量表达式只会匹配一次,匹配成功后就会执行该分支后的所有语句,如果不加break,则会将后面所有分支的语句执行完!!

4)多个 case 分支可以共用一组执行语句,如下:

case 'A':
case 'B':
{
    printf....
}
4.while循环
语法格式:

while (表达式)
{
语句
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltbZY2r2-1693146368197)(E:\Git_study\LinuxC\C\20210817215700216.png)]

 5.do...while循环
语法格式:

do
{
    语句
}while(表达式);
 与while不同的是,do..while的循环体至少会被执行一次

6.for循环
语法格式:

for ([表达式1];[表达式2];[表达式3])
    语句
break和continue
break用于终止当前层次的循环,而continue用于结束本次循环,转入下一次循环

数组

构造类型之一,连续存放

一维数组

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

初始化

​ 不初始化,全部初始化,部分初始化,static

  • 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));
  }
}
//打印地址符,%p
//strlen是用来求一个字符串(string)的长度

1.定义数组时,要先明确数组的类型(int还是char或者其他类型)。
2.sizeof不能用来计算已经定义好长度的数组的数组长度(如int arr[10] = {0},不能有中括号里的10)。
3.strlen不能用来计算int类型的数组长度。
4.int型计算数组长度,最好用 sizeof(arr) / sizeof(arr[0]) 来计算数组长度。
5.char型数组如果要计算数组长度,一定要用双引号赋值。
6.char型数组如果要用大括号赋值,一定要注意数组最后是否需要结束标志 \0


数组越界

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]);
  }
}

二维数组

  1. 定义,初始化:[存储类型] 数据类型 标识符 【行下标】【列下标】

        2.元素引用:数组名【行标】【列标】
    

​ 3.存储形式:顺序存储,按行存储

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*));
        printf("%p-->%d",&a[i][j],a[i]a[j]);
    }
  }
}

//数组没初始化,就是没往里面写值,原来每位的电平是啥就是啥
//a+1  看作: 归属为a的一维数组的基本元素的1个单位长度
//行指针
深入理解二维数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WOMyIF0s-1693146368197)(E:\Git_study\LinuxC\C\image-20230805104538945.png)]

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

字符数组

定义,初始化,存储特点

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

​ 单个字符初始化;用字符串常量初始化

注意部分初始化的时候,尾部会自动初始化为’\0’;这个出错的情况是不确定的,例如如果有一个变量的地址刚好在str[N]的后面,你的输出也没问题,但是那个变量的内容就被改了,之后再访问那个变量就是乱码,又如你的输入特别长,遇到一块不可写的内存,会段错误

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);
}
//strlen以尾零为结束标记  sizeof计算所占内存全部大小
//char *strcpy(char *dest, const char *src);
//char *strncpy(char *dest, const char *src, size_t n);
//char *strcat(char *dest, const char *src);
//char *strncat(char *dest, const char *src, size_t n);
//int strcmp(const char *s1, const char *s2);
//int strncmp(const char *s1, const char *s2, size_t n);

多维数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vx8JTCL3-1693146368198)(E:\Git_study\LinuxC\C\image-20230806194815821.png)]

指针

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

变量与地址

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

指针与指针变量

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


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqD75Uzq-1693146368198)(E:\Git_study\LinuxC\C\image-20230806201226144.png)]

直接访问与间接访问

i = 1;
&i = 0x2000;
p = 0x2000;
&p = 0x3000;
*p = 1;
q = 0x3000;
&q = 0x4000;
*q = 0x2000;
**q = 1;
//要翻译回来的,类型不一致了就翻译不回来咯
//指针类型用来使用数据时正确的分割内存单元

//不管什么类型几级的指针,它在某一平台所占的内存大小是确定的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6G4zkOLt-1693146368198)(E:\Git_study\LinuxC\C\image-20230812102659888.png)]

空指针与野指针

void func(){
    int *p=NULL;
    printf("%d\n",*p);

}

空类型

char *s = "hello";
void *i = s;//不确定什么类型就用void空类型

定义与初始化的写法

指针运算

& * 关系运算 ++ –

指针与数组

指针与一维数组

#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));
  }
}
//a[i]: a[i]=*(a+i)=*(p+i)=p[i]
//&a[i]: &a[i]=a+i=p+i=&p[i]

//p++!= p+1
//因为p++ == p = p+1,已经发生了赋值操作

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++));
  }
}
//printf是从右边开始取内容,所以刚刚先取了*p++导致p又自增了1,所以从4开始而不是0
//*和++同级运算啊,并列按自右向左的顺序运算。
//int * p=(int [3]){1,2,3};匿名数组

指针与二维数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gADMCmqT-1693146368198)(E:\Git_study\LinuxC\C\image-20230814213143790.png)]

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

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

   //p=&a[i][j];*(a+0);*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));
    }
  }
}
//因为二维数组*完是一维数组
//行指针和列指针,帮助理解的方言
//因为a实际存储的是每一列的首地址,a是个二级指针
//取*为啥是降级变成列指针
#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));
    }
  }
}

指针与字符指针

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
   char str[]="Hello";//字符数组
    //str="World" 典型错误
    char *str1="hell0";//字符指针
    printf("%d %d",sizeof(str1),strlen(str1));
    //4 5  前面是输出一个字符指针的内存长度
    //strcpy(str1,"world"); 想覆盖字符串常量??no
    str1="world";
    puts(str);
}

const与指针

首先,来看看const的基本含义。在 C/C++ 语言中,const关键字是一种修饰符。所谓“修饰符”,就是在编译器进行编译的过程中,给编译器一些“要求”或“提示”,但修饰符本身,并不产生任何实际代码。就 const 修饰符而言,它用来告诉编译器,被修饰的这些东西,具有“只读”的特点。在编译的过程中,一旦我们的代码试图去改变这些东西,编译器就应该给出错误提示。
    所以,const修饰符的作用主要是利用编译器帮助我们检查自己代码的正确性。我们使用const在源码中标示出“不应该改变”的地方,然后利用编译器,帮助我们检查这些地方是否真的没有被改变过。如果我们不小心去修改了这些地方,编译器就会报错,从而帮助我们纠正错误。使用const和不使用const,对于最终编译产生的代码并没有影响。

虽然const对于最终代码没有影响,但是尽可能使用const,将帮助我们避免很多错误,提高程序正确率。

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

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

如何区分指针常量和常量指针,看const在后还是前,*在前指针常量,*在后常量指针

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

int const *p;

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

int *const p;

const int *const p;
//const int *const p;常量指针常量

指针数组与数组指针

指针数组

//我的理解,无论是指针数组(数组),数组指针(指针)强调的都是后者

// 【存储类型】 数据类型 * 数组名 【长度】 
//int *arr[3]--》type name;-->int *[3] arr;
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 (*p)[3];--》type name;-->int[3] *p;
//这样指针移动一次的距离就是一个数组的大小;
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(int argc,char *argv[]) {
  exit(printf("Hello!\n"));//7
}
//echo $? 返回上一个命令的状态,0表示没有错误,其它任何值表明有错误
//agrc是计数器,来计算终端传了多少个参数过来;
//argv这个指针数组有多少元素,取决于argc是多少

//c语言中最好主调函数在被调函数下面,不然会有警告

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXzWpZNh-1693146368199)(E:\Git_study\LinuxC\C\image-20230816205029075.png)]

函数的传参

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

函数的调用

  • 嵌套
#include <stdlib.h>
#include <stdio.h>
int max(int a, int b, int c);//函数声明

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);
}

函数与数组

image-20230819141438841
#include <stdlib.h>
#include <stdio.h>

// 注意这里的int *arr 的大小是8个字节 是一个普通的指针不是数组 所以一定要传大小
//形参时int p[] 等同与int *p都是指针
//在形参形式定义时,其实一个[] 就等价于*;二维也是这样
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);
}

//今天遇到了一段代码对赋值运算符之后的表达式进行判断,由于之前没接触过类似的代码,特此记录。

if ((pTmpBuf = pBuf) != NULL)

结论:  C/C++中 (A = B) 返回得到是赋值号(=)的左面的值

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3pHCy7Y-1693146368199)(E:\Git_study\LinuxC\C\image-20230819145110344.png)]

函数的指针

  • 指针函数
//    类型 * 指针名 (形参);
//如: int *  fun(int);
#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));
  }
}
/*函数名是地址吗
我们经常把函数名当地址用,可实际上他不是地址,但是你可以把他理解成地址,心里清楚他不是就行。
对于test和&test你应该这样理解,test是函数的首地址,它的类型是void (),&test表示一个指向函数test这个对象的地址,它的类型是void (*)(),因此test和&test所代表的地址值是一样的,但类型不一样。test是一个函数,&test表达式的值是一个指针!*/

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

    ​ 如:int ( *p )(int);

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oR439BUl-1693146368199)(E:\Git_study\LinuxC\C\image-20230819182222168.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVDeSlGb-1693146368199)(E:\Git_study\LinuxC\C\image-20230819182303198.png)]

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

    ​ 如:int (* arr[N]) (int);

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-satfjstC-1693146368200)(E:\Git_study\LinuxC\C\image-20230819184022607.png)]

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

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

实际例子

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

构造类型

结构体

1 产生及意义

描述复杂的数据类型

2 类型描述

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

3 嵌套定义

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

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

4 定义变量(变量,数组,指针) 初始化以及成员引用

  • 结构体 .
  • 结构体指针 ->
//成员引用:变量名.成员名;   指针->成员名
//		  (*指针).成员名
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 = &a_; // 部分初始化
  //还可以定义结构体数组
  
  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
}

占用内存空间大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jh6zStHz-1693146368200)(E:\Git_study\LinuxC\C\image-20230822212958596.png)]

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));//4466 
}
//结构体变量和共用体变量都是正常变量,如果要用指针变量取地址得用取地址符&
//共用体和结构体很多地方都是大同小异

另一种写法

#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);
}

占用内存空间大小

函数传参(值,地址)

位域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DlLwqg4a-1693146368200)(E:\Git_study\LinuxC\C\image-20230826131938301.png)]

union {
  struct {
    char a:1;
    char b:2;//2位
    char c:1;
  }x;
  char y;
}w;//该共用体一个char型一个字节大小
int main() {
  w.y=6;
  printf("%d\n", w.x.b);//-1
  exit(0);
}

枚举

enum 标识符{
  成员1,
  ...
};
enum dar {
  MON = 1,//取了第一个值,后面的值会顺次增加
  TUS,  //2
  WEB,
  THR,
  FRI =1,
  SAT,  //2
  SUN,
};

int main() {
  enum day a = FRI;
  
  printf("%d\n", a);
}
enum status {
  STATE_RUNNING = 1,
  STATE_CANCELED,
  STATE_OVER,
};

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

int main() {
  struct job_st job1;
  
  switch(jobs.state) {
    case STATE_RUNNING:
      // TODO
      break;
    case STATE_CANCELED:
      // TODO
      break;
    case STATE_OVER:
      // TODO
      break;
    default:
      abort();
  }
}

动态内存管理

  • malloc

  • calloc

  • realloc

  • free

    原则:谁申请谁释放,在同一模块成对申请释放,就像IO的使用一样

   // void *malloc(size_t size);
  //  void free(void *ptr);
   // void *calloc(size_t nmemb, size_t size);
   // void *realloc(void *ptr, size_t size);
#include <stdlib.h>
#include <stdio.h>

int main() {
  int *ip = malloc(sizeof(int));
	if(ip==NULL){
    	printf("malloc() error!\n");
    }
    
  *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) {
  p = malloc(size);
  if(p == NULL) {
    return;
  }
}

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;
 //p在指向NULL时,不能对NULL这块空间进行写操作
 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); // 传递二级指针
    
  printf("%p-->%d\n", *p); 
  free(p);
 // p = NULL;//如果没重新分配空间,会报段错误(core dumped)
  
  *p = 123;
  printf("%p-->%d\n", *p); // 这个指针已经是野指针了 
  
  exit(0);
}
  • free代表着变量p不再拥有原来指向内存空间的引用权限
  • free后最好马上将指针置NULL

typedef

typedef type typename

typedef(type define):为已有的数据类型改名

//typedef 已有的数据类型 新名字;
typedef int INT

int main() {
  INT i = 9;
}

typedef 和 define 的区别,一个别名一个是替换

#define IP int *
type int *IP;

int main() {
  // #define
  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);--> int(int) FUNC;
//FUNC f; --> int f(int);

函数

typedef int *FUNC(int);
FUNC p; -->int *p(int);

指向指针函数

typedef int* (*FUNCP)(int);
//FUNCP p;  --> int *(*p)(int);

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
clean:
  rm *.o mytool -rf
OBJS=main.o tool1.o tool2.o 
CC=gcc  //默认就是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
 //rm -f

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

CFLAGS+=-Wall -g -c     //GCC编译选项CFLAGS参数
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

- **free代表着变量p不再拥有原来指向内存空间的引用权限**
- **free后最好马上将指针置NULL**

# typedef

`typedef type typename` 

typedef(type define):为已有的数据类型改名

```c++
//typedef 已有的数据类型 新名字;
typedef int INT

int main() {
  INT i = 9;
}

typedef 和 define 的区别,一个别名一个是替换

#define IP int *
type int *IP;

int main() {
  // #define
  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);--> int(int) FUNC;
//FUNC f; --> int f(int);

函数

typedef int *FUNC(int);
FUNC p; -->int *p(int);

指向指针函数

typedef int* (*FUNCP)(int);
//FUNCP p;  --> int *(*p)(int);

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
clean:
  rm *.o mytool -rf
OBJS=main.o tool1.o tool2.o 
CC=gcc  //默认就是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
 //rm -f

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

CFLAGS+=-Wall -g -c     //GCC编译选项CFLAGS参数
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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值