C语言快速入门

Linux C 的编译流程

C 编译器

  • gcc GNU
  • msvc windows
  • clang 苹果
  • intel
  • cc 默认Linux c语言编译器

程序设计语言分类

  • 编译型 C、C++、java
    • 把源代码转换成机械指令(X86 电脑、ARM 手机)
    • 编译做了类型安全检查,安全
    • 性能高
    • 灵活差
  • 解释型 Python、JS、java
    • 源代码直接在解释器上运行
    • 灵活性强、建模与语义的表达高效 ‘*’
    • 易学习 ‘*’
    • 性能差

编译流程

c语言源文件:
.h 头文件 包含函数和变量声明、宏和数据类型定义
.c 源文件 包含函数定义和实现,预处理语句

gcc hello.c -o hello

以上语句执行过程可拆分为一下流程:

  1. 汇编,生成汇编代码
    gcc -S hello.c -o hello.s

  2. 编译,生成目标文件.o/.obj
    gcc -c hello.s -o hello.o

  3. 链接 目标文件、库文件(.so/.dll)和操作系统系统执行相关代码 生成可执行文件 hello/hello.exe
    gcc hello.o … -o hello

TCC 编译器
tcc 优化编译器,生成可执行文件较小

C 语言学习

设计者:丹尼斯·里奇 & 肯·汤普森 共同设计实现 UNIX,设计C语言
UNIX/C、C/Linux、Linux/互联网共生相互成就

推荐纪录片《操作系统革命》

CGI 用C动态生成HTML页面
Web/PHP 基于c改进的

编程范式(方法学、建模时思维模式)

  • 面向过程:过程、操作,C、Pascal
  • 面向对象 OOP:C++、java、python
  • 函数式:Lisp

学习内容

  • 变量与类型系统
  • 流程控制:顺序、分支、循环
  • 数组
  • 函数:程序基本单位;C++中为类、对象
  • 指针 ‘*’ 信任开发者,驾驭起来有难度 unix/linux root用户
  • 结构体
  • 库:文件、网络、进程、线程(提高性能,如找一个亿以内的质数)、GUI、安全、MySQL、Opencv …

推荐书:《C语言程序设计 现代方法》

C变量与类型

程序,为完成特定任务,数据与指令的集合

变量与常量

标识符,定义了内存中的一个区域,替代内存物理地址

变量:空间中数据随时间会发生变化
常量:程序中随时间不发生变化 ‘const’

变量声明:类型     标识符
          |         |
  空间分配的大小   字母下划线开头
  空间格式         包含字母、下划线、数字
                  不能使用关键字、保留字
                  不能包含空格以及下划线以外的其他符号
                  变量小写、常量大写
                  有意义

类型系统

  • 基本类型
    • 数值
      • 整数
      short 2     2^16 = 65536
      int   2 4   2^32 = 46亿 10位
      long  4 8
      long long 8
      
      • 浮点数
        float 4
        double 8
    • 字符
      • char 1
    • 逻辑
      • bool 1
  • 复合类型
    • 数组
      复合数据类型,存储多个值,大小不可变
       int a[6]; // 6个int元素
       char c[128]; // 128个char字符
       char c[] = {'a', 'b', 'c'}; // 3个字符
       int a[6] = {1, 2, 3, 4, 5, 6}; // 6个int元素
       int n; int a[n]; // n个int元素,n改变a大小不变, c++中n必须是const
    
          下标可以越界但是结果不确定
    
    • 结构体
    • 联合

静态与动态类型

  • 静态类型 C、C++、java
  • 动态类型 Python、JS

运算

  • 算术:+ - * / %

  • 位(二进制):& | ^ << >> ~

  • 逻辑(比较):

    • || 或 左边为假才执行右边,两个假为假
    • && 与 左边为真才执行右边,两个真为真
    • ! 非 真变假,假变真
  • 关系(真假):== != > < >= <=

  • 赋值:从右往左执行

  • 位运算
    数值转换成二进制进行运算,效率更高

    • & 与:两1得1,否则为0

      • 掩码运算 ip地址掩码
      • 奇数偶数判断(n & 1 == 1 为奇)
    • | 或:两0得0,否则为1

      • 状态叠加
    • ^ 异或:两1得0,两0得0,否则为1

      • 在所有成对出现的数字中,找只出现一次的数字
      • 交换两个变量的值 a = a ^ b; b = a ^ b; a = a ^ b;
    • ~ 非:0变1,1变0

    • '>>'右移:右移n位,相当于除以2的n次方 比除法性能高

    • << 左移:左移n位,相当于乘以2的n次方

流程控制

程序结构

  • 顺序

  • 分支(判断选择)

    • if
        if (条件) {
    
        }
    
    • if else (三元运算符可替代 '? : ')
        if (条件) {
    
        }else {
    
        }
    
    • if else if else (多路分支,匹配范围、枚举值/精确值)
        if (条件) {
    
        }else if (条件) {
    
        }else {
            //逻辑闭环
        }
    
    • switch case (多路分支、有限:只能匹配枚举值/精确值)
        switch (变量) {
            case:
                break;
            ......
            default:
                //逻辑闭环
        }
    
  • 循环

    • for 循环次数确定
    //1 初始化
    //2 循环条件
    //3 步长
    //4 循环题
    //执行顺序1->2->4->3->2->4->3...
    for (1; 2; 3){
        //4
    }
    
    
    • while 循环次数不确定, 先判断再执行
    while (条件){
    
    }
    
    • do while 循环次数不确定,先执行再判断
    do {
    
    }while (条件);
    

重构

不增加代码功能,对代码结构进行改变

函数

命名的代码块,预定义的,可复用的功能单元
函数是c程序的基本单元,c程序有若干个函数组成
函数也叫方法

函数大小:建议不要超过100行/一个屏幕

函数的作用

  1. 封装:将功能封装成一个函数,便于复用,开放闭合原则
  2. 可复用
  3. 模块化:将一个大的功能分解成若干个小功能,便于理解,一般在不同的文件中定义函数

开放闭合原则:对扩展开放,对修改关闭

定义


    //函数名 functionName
    //返回值类型 returnType void int double ptr...
    //参数列表 parameter list int a, int b double c, int d...
    returnType functionName (parameter list){
        //函数体
        //返回值
        return returnValue;
    }

递归

一个函数调用自身

部分递归实现的算法可以使用循环实现,递归代码更简洁
部分循环无法实现的只能使用递归实现 文件遍历、图、汉诺塔

  1. 递归函数必须有收敛条件
  2. 包含自身的调用

模块化

/*
* f.h
* 声明函数
*/
int jc(int);
int fib(int);
long long fib2(int);

/*
* f.c
* 定义函数
*/
#include "f.h"
//菲波那切数列
int fib(int n){
    //
}

long long fib2(int n){
   //
}
//

指针

概念

指针:一种数据类型,存储变量的地址

指针可以运算

定义

    int i;    //64位 4字节 32位 2字节
    double d; //8字节
    // 64位 8字节 32位 4字节
    int *p = &i; //& 取地址符 p保存首地址,int类型规定管辖范围
    double *p1 = &d;

    // %p打印地址 *解引用运算符,可以取得指针指向的数据,在声明时表示变量是一个指针
    printf("int\t%ld %ld %p %d\n", sizeof(i), sizeof(p1), p1, *p1);
    printf("double\t%ld %ld %p %lf\n", sizeof(d), sizeof(p3), p3, *p3);

    int a[] = {100, 200, 300, 400}; //数组是复合类型,数组名就是一个指针,指向数组的首地址,不被复制 (a = b 错)
    int *p;
    p = &a;     //指针指向数组的首地址
    p = a;      //数组名称转化成数组首地址
    p = &a[0];  //指向数组首元素的地址,就是数组首地址

    p[i] = 233; //p[i] == *(p+i)
    printf("%d\n", *(p + i)); //输出233

字符串

定义

字符串:字符构成的一个有‘序列表’,以‘\0’结尾的字符序列

//字符
char c;
//字符数组,字符串中出现特殊字符需要用 \ 转义
char s[] = {'a', 'b', 'c', '\0'};
char s[4] = "abc"; //由于隐含地包含'\0',容量(sizeof(s))要大于表面上的‘长度’(strlen(s)),这里大于4
//字符串,数组,元素内容可变(s1[0] = 'a'),数组不可变(s1 = "abc" X)
char s1[] = "abc";
//字符指针,不改可变元素内容(s2[0] = 'a' X),指针变量本身可变(s2 = "abc")
char *s2 = "abc";
//拼接,初始化的时候可以
char s3[] = "abc""def"
            "ghi\
get";
//拼接,赋值时也可以
char *s2 = "abc""def"
s2 = "abc""get";

sizeof(s):返回字符数组容量,包含‘\0’
strlen(s):返回字符数组长度,不包含‘\0’

数据形式:

  • 数值
  • 字符串:文本
  • 二进制
    • 图形
    • 音频
    • 视频

操作

头文件:string.h

  • strlen(s):返回字符串长度
  • strcat(s1, s2)、strncat(s1, s2):拼接字符串,s2拼接到s1
  • strcmp(s1, s2):比较字符串, 返回值:0(相等),-1(s1 < s2),1(s1 > s2), 其实就是返回最后一个不相等的字符做减法运算后的结果
  • strcpy(s1, s2)、strncpy(s1, s2):复制字符串, s2复制到s1
  • puts(s)、fputs(s, fp):输出字符串到标准输出或文件,回车结束
  • gets(s)、fgets(s, size, fp):从键盘读取字符串,回车结束,最多取size - 1个字符(包括\0)

应用

  1. 进制转换
    char base[] = "0123456789ABCDEF";
    char res[100];
    int n;
    printf("请输入一个正整数:");
    scanf("%d", &n);
    int len = 0;
    while(n > 0){
        char tmp[100];
        tmp[0] = base[n % 16];
        tmp[1] = '\0';
        strncat(tmp, res, len);
        ++len;
        strncpy(res, tmp, len);
        n /= 16;
    }
    printf("%s\n", res);
  1. 大数值计算
  • 加法
    • 从低位到高位每位相加再加进位,模10的余数放进结果字符数组,除10的结果保存为进位,字符数组长度就是较大数长度 + 1(最多进一位)
    • 为方便操作,对不等长的两个数,将较短的数补充0,使其与较长数长度+1相等,较长数也要在最高位之后补充一个0,从低位到高位加,要将字符数组翻转
    • 结果要删除多余的0
  • 减法
    • 不管两个数谁大谁小,用大数做减数,小数做被减数,最后根据大小处理符号
    • 从低位到高位每位减,结果小于0要向高位借位(结果加10,高位a[i + 1]减1)
    • 为方便操作,对不等长的两个数,将较短的数补充0,使其与较长数长度相等,同时要将字符数组翻转
    • 结果要删除多余的0
  • 乘法
    • 对于被乘数每一位b[i],乘数a累加b[i]次存入tmp,然后tmp后面补i个零,将tmp累加到结果数组中
  • 除法
    方法一: 大数减小数,能减多少次除数商就是多少
    方法二: 模拟短除法的过程
    • 先处理特殊情况:1- 被除数比除数小,直接输出0 2- 被除数为0,打印错误信息
    • 从高位开始模拟,先取除数a与被除数b长度相等的一部分tmp
    • 从高位开始tmp减被除数b,结果放tmp,能减的次数count就是结果数组这一位的值,最后不够减的数保存在tmp
    • 将a剩余拼接一位到tmp末尾,直到大于除数,或者拼接的是被除数最后一位之后的‘\0’(退出条件),每拼接一位就保存一位0到结果数组,但是结果数组的第一位不能是0,所以如果时一开始的结果,结果没有内容不需要补0,同时遇到退出条件也不用补0
    • 重复2-3步,共执行 除数长度减被除数长度 + 1 次
    • 为了删除结果多余的0,如果count是0且结果数组是第一位,则不要加到结果中,如果更新结果数组后已经是最后一次循环直接退出
    • 算法过程要多次比较两个数的大小,可以定义一个比较函数比较两个字符串表示的数的大小

示例代码:

//交换两个字符
void swap(char *a, char *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
//字符串翻转
void reverse(char *s)
{
    int len = strlen(s);
    for (int i = 0; i < len / 2; ++i)
    {
        swap(&s[i], &s[len - i - 1]);
    }
}
//字符串表示的数字相加运算
void add(const char *a, const char *b, char *res)
{
    int llen = strlen(a);
    int rlen = strlen(b);
    //记录最后结果是正是负
    int flag = 0;
    if (a[0] == '-' && b[0] == '-')
    {
        flag = 1;
        --llen;
        --rlen;
    }//如果一正一负则委托减法进行运算
    else
    {
        if (a[0] == '-' && b[0] != '-')
        {
            sub(b, a + 1, res);
            return;
        }
        if (a[0] != '-' && b[0] == '-')
        {
            sub(a, b + 1, res);
            return;
        }
    }
    //两个都是整数则正常计算


    //拷贝到临时变量中,避免影响原字符串
    char tmpa[100];
    char tmpb[100];
    if (flag)
    {
        strncpy(tmpa, a + 1, llen + 1);
        strncpy(tmpb, b + 1, rlen + 1);
    }
    else
    {
        strncpy(tmpa, a, llen + 1);
        strncpy(tmpb, b, rlen + 1);
    }
    //计算出最较大的和较小的数字以及其长度
    int size, minc;
    char *max, *min;
    if (llen > rlen)
    {
        max = tmpa;
        size = llen + 1;
        min = tmpb;
        minc = rlen;
    }
    else
    {
        max = tmpb;
        size = rlen + 1;
        min = tmpa;
        minc = llen;
    }
    //翻转数组
    reverse(tmpa);
    reverse(tmpb);
    //填充0使原字符串最大数字的长度加1
    for (int i = minc; i < size; ++i)
    {
        min[i] = '0';
    }
    max[size - 1] = '0';
    //逐位加
    int cur = 0;
    for (int i = 0; i < size; i++)
    {
        cur = cur + max[i] - '0' + min[i] - '0';
        res[i] = cur % 10 + '0';
        cur /= 10;
    }
    //去除多余的0
    while (res[size - 1] == '0')
    {
        --size;
    }
    if (flag)
    {
        res[size++] = '-';
    }
    //补充'\0'
    res[size] = '\0';
    //翻转位正常循序
    reverse(res);
}
//比较两个字符串表示的数组
int compare(const char *a,const char *b)
{
    int llen = strlen(a);
    int rlen = strlen(b);
    if (llen > rlen)
    {
        return 1;
    }
    else if (llen == rlen)
    {
        return strcmp(a, b);
    }
    else
    {
        return -1;
    }
}

//字符串表示的数字相减运算
void sub(const char *a, const char *b, char *res)
{
    int flag = 0;
    int llen = strlen(a);
    int rlen = strlen(b);
    //使用临时数组,不影响原字符串
    char tmpa[100], tmpb[100];
    strncpy(tmpa, a, llen + 1);
    strncpy(tmpb, b, rlen + 1);
    //比较两个数大小,获取长度,用较大减较小数,最后处理符号
    //分别获取较大数和较小数
    char *max, *min;
    int minc, maxc;
    //根据符号处理
    if (a[0] == '-' && b[0] == '-')
    {
        --llen;
        --rlen;
        if (compare(a + 1, b + 1) >= 0)
        {
            flag = 1;
            max = tmpa + 1;
            min = tmpb + 1;
            maxc = llen;
            minc = rlen;
        }
        else
        {
            max = tmpb + 1;
            min = tmpa + 1;
            maxc = rlen;
            minc = llen;
        }
    }//如果一正一负则委托加法法进行运算
    else if (a[0] != '-' && b[0] != '-')
    {
        if (compare(a, b) >= 0)
        {
            max = tmpa;
            min = tmpb;
            maxc = llen;
            minc = rlen;
        }
        else
        {
            flag = 1;
            max = tmpb;
            min = tmpa;
            maxc = rlen;
            minc = llen;
        }
    }
    else
    {
        //-a - b =-a + -b
        if (a[0] == '-' && b[0] != '-')
        {
            tmpb[0] = '-';
            strncpy(tmpb + 1, b, rlen + 1);
            add(a, tmpb, res);
            return;
        }//a - -b = a + b
        if (a[0] != '-' && b[0] == '-')
        {
            add(a, b + 1, res);
            return;
        }
    }

    //翻转数组
    reverse(max);
    reverse(min);
    //较小数填充0
    for (int i = minc; i < maxc; ++i)
    {
        min[i] = '0';
    }
    //逐位减
    int t = 0;
    for (int i = 0; i < maxc; ++i)
    {
        t = max[i] - min[i];
        if (t < 0)
        {
            t += 10;
            --max[i + 1];
        }
        res[i] = t + '0';
    }
    //去除多余0
    while(res[maxc - 1] == '0')
    {
        --maxc;
    }
    //添加\0和符号
    if (flag)
    {
        res[maxc++] = '-';
    }
    res[maxc] = '\0';
    //翻转成正常顺序
    reverse(res);
}
//大数乘法
void mul(char *a, char *b, char *res){
    int llen = strlen(a);
    int rlen = strlen(b);
    //初始化结果
    int size = llen + rlen + 1;
    memset(res, '0', size);
    res[1] = '\0';
    //不改变原数组使用临时变量
    char tmpa[llen + 1], tmpb[rlen + 1];
    strncpy(tmpa, a, llen + 1);
    strncpy(tmpb, b, rlen + 1);
    
    int min = llen > rlen ? rlen : llen;
    char *minc = llen > rlen ? tmpb : tmpa;
    int max = llen > rlen ? llen + 1 : rlen + 1;
    char *maxc = llen > rlen ? tmpa : tmpb;
    //翻转较短字符串
    reverse(minc);
    //对较短字符串每一位n,较长位自加n次, 再添加i个0后加到结果中
    for (int i = 0; i < min; ++i){
        char tmp[size];
        strncpy(tmp, maxc, max);
        int n = minc[i] - '0';
        //自加n次
        for (int j = 0; j < n - 1; ++j){
            add(tmp , maxc, tmp);
        }
        //在末尾添加i个零
        int pos = strlen(tmp);
        memset(tmp + pos, '0', i);
        tmp[pos + i] = '\0';
        //累加到结果中
        add(res, tmp, res);
    }
}
//大数除法
void divi(char *a, char *b, char *res){
    //特殊情况
    if (b[0] == '0'){
        printf("除数不能为0\n");
        exit(-1);
    }
    //有负数的除法转换成其绝对值相除
    int flag = 0;
    if (a[0] == '-')
    {
        ++a;
        flag = 1;
    }
    else if (b[0] == '-'){
        ++b;
        flag = 1;
    }
    int t = 0;
    if (flag)
    {
        res[t++] = '-';
    }
    if (compare(a, b) < 0){
        res[0] = '0';
        res[1] = '\0';
        return;
    }
    //从高位模拟,高位减被除数,能减多少次就是商这一位的值

    //取除数与被除数长度相等的一部分
    int llen = strlen(a);
    int rlen = strlen(b);
    char tmp[rlen + 1];
    strncpy(tmp, a, rlen);
    tmp[rlen] = '\0';

    for (int i = rlen;; ++i){
        //记录能减几次
        int count = 0;
        while (compare(tmp, b) >= 0){
            //减去被除数
            sub(tmp, b, tmp);
            ++count;
        }
        //count是0 且是结果第一位不用加到结果
        if (count != 0 || t != flag){
            res[t++] = count + '0';
        }
        //到达最后一位,直接退出
        if (i >= llen){
            break;
        }
        //拼接剩余数字
        while(1){
            strncat(tmp, a + i, 1);
            ++i;
            //直到大于除数,或者拼接的是被除数最后一位之后的‘\0'
            if (compare(tmp, b) >= 0 || i == llen + 1){
                break;
            }
            //不是结果的第一位才要补零
            if (t == flag){
                continue;
            }
            res[t++] = '0';
        }
    }
    res[t] = '\0';
}

结构体

C结构体与C++类的区别

结构体:封装了属性(字段)
类:封装了属性和相关操作

结构体
方法学面向过程面向对象
字段++
函数/方法-+
继承-+
赋值操作拷贝指针

c++中结构体和类没有区别,结构体是成员的默认访问级别为公有的类

结构体定义

// 把多个相关的字段封装成一个整体
struct student
{
    char name[16];
    char tel[11];
    int score;
    double height;
    double weight;
    struct student *friend;
}

struct student s1[40];
// 不仅封装数据还封装方法
class Student {

    int age;
    int score;

    void talk() {
    }

    void run() {
    }
}

结构体填充原则

  • 各字段字节对齐
  • 结构体整体大小是最长字段的大小

结构体操作

  1. 赋值
struct date d1;
struct date d2 ={2024, 7, 3};
struct date d3 = {.month = 7, .day = 4};
d1.year = 2024;
d1.mouth = 7;
d1.day = 5;
  1. 使用
//访问,通过指针(d->year (*d).year)或对象访问(d.year)
void display(struct date *d){
    printf("%d年%d月%d日\n", d->year, d->month, d->day);
}
void display2(struct date d){
    printf("%d年%d月%d日\n", d.year, d.month, d.day);
}

内存管理相关

内存管理概念

    int a;
    int *p;    //8字节
    double *d; //8字节
    
    void *v;   //8字节,类型未知的一个指针变量,不可以直接使用,通过类型转换或者赋值给具体类型指针使用

void*:类型未知的一个指针变量,可以存储任何类型的地址,不可以直接使用,通过类型转换或者赋值给具体类型指针使

内存管理方式

  • 系统管理
    函数中声明的普通变量,内存空间在栈空间
    由系统自动分配和销毁,在函数声明时分配,结束时销毁
  • 手动管理
    基于内存管理函数创建的,或全局的变量,存储在堆空间
    需要手动分配和销毁,若不释放会引起内存泄漏

相关函数

stdlib.h

  • malloc(size_t size) 分配
    //静态数组,大小不变
    int a[4] = {100, 200, 300, 400};
    //动态数组,大小可变
    int *b = malloc(sizeof(int) * 4);
    
  • free(void *ptr) 释放
  • calloc(size_t num, size_t size) 分配并初始化
  • realloc(void *ptr, size_t size) 重新分配 如果有剩余空间,则在原地址中扩展,否则开辟新的空间并拷贝至新空间
  • memset(void *ptr, int value, size_t num) 初始化
  • memcpy(void *dest, void *src, size_t num) 内存拷贝
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值