C语言学习笔记

一、前言

  1. 什么是程序?

    1. 计算机程序:计算机PC、嵌入式领域

    2. 程序:数据结构和算法(沃斯

      • 数据结构:对数据的描述(数据的类型和组织方式
      • 算法:对操作的描述,嵌入式主要是编写逻辑型和数学型算法(算法工程师研发数据处理大模型
    3. 进程/任务:跑起来的程序叫做进程,动态描述

  2. 程序怎么来的

    编写、编译(预处理、编译、汇编、链接)、执行

  3. 计算机语言

    1. 机器语言——光电输入机读取穿孔纸带信息
    2. 符号语言——汇编
    3. 高级语言——if、else…

    C语言面向过程
    C++、java、PHP面向对象,类
    通俗解释:洗衣服的例子

  4. 学习过程

    1. 看视频不要倍速
    2. 视频中的代码(理解、对着打、默打、积累编译错误提示、错误代码不要放弃不断调试、多总结[ csdn 写博客] )
    3. 算法bug、目的是能编程 做东西、考核标准:链表嵌套做学生成绩管理系统

关于CMD窗口的常用命令:

cls // 清屏
Ctrl + C // 终止命令

gcc demo.c -g	// 编译
gdb a.exe		// 调试
r				// 运行
q				// 退出

二、初识

  1. IDE编辑器

    Notepad++编辑 CMD窗口运行

    CMD:①gcc 文件名.c 编译 ②a.exe 运行

    stdio.h 声明了printf 预处理替换了include的内容

  2. 数据的表现形式

    1. 变量四要素变量名、变量值、变量类型和存储单元( 存储位置 )

    2. 命名:字母数字下划线 区分大小写 不要用 int a;
      C语言命名变量方法:匈牙利、驼峰法、帕斯卡、下划线法

      驼峰命名法 变量名顾名思义 变量secondsPerYear 函数Second_Per_Year 为了防止和系统变量冲突会以 _ 开头命名 _students

    3. C语言数据类型:
      请添加图片描述

      各个类型数据含义在这里插入图片描述

    4. 基本类型:整型、字符型、浮点型

      整型和字符型可以通过ASCII码联系起来, 字符型可以用整型表示

      int data1 = 10;
      char data1 = 'a'; 			 // 必须加单引号
      // ASCII码   A——65  a——97  
      

      在Linux或者64位操作系统 int 和 float 4个字节 char 1个字节 1个字节==8位

      单片机中 int 2个字节

    5. 输入输出

      printf 格式控制和输出列表 %d %c %s 字符串 %f %x (0x )

      scanf 格式控制和地址列表 &a 一定要加 &

      scanf(“%d%d%d”, a, b, c); 输入的时候回车或者空格隔开 原样输入.

      数据输出格式控制符
      在这里插入图片描述
      bool类型也用%d输出

      scanf格式控制不要加逗号以及其他控制字符,多个变量输入用多个scanf

      scanf输入多个整型数据 空格 回车可以做分隔符, scanf输入多个char数据 空格 回车 tab 不能当分隔符

      scanf混合输入%d%c%f, 输入的时候一定要对应类型

      其他输入输出:putchar() getchar() puts() gets()

      tipsputs和printf区别:puts自动添加换行符,而printf支持花样输出

      代码太长了换行,提高代码可读性代码分行显示 反斜杠 \

    6. 大小写字母转换

      getchar(); 					// 从键盘上获得一个字符
      putchar(getchar()); 		// 打印从键盘上获得的字符
      

三、流程控制

  1. 控制语句:

    if(){---}else{---}  // 条件语句
    for(){---}			// 循环语句
    while(){---}		// 循环语句
    do{---}while()		// 循环语句
    continue			// 结束本次循环语句
    break				// 终止switch或者循环语句
    switch				// 多分支选择语句
    return				// 从函数返回语句
    
    for(;;); // 死循环
    
  2. 关于switch:

    switch判断条件为整型,可以把数据通过 加减乘除 取余等运算得到整型条件

    // switch
    switch( lucheng / 250 ){
     case 0:
         puts("zhekou1");
         break;
     case 1:
     case 2:
     case 3:
         puts("zhekou2");
         break;
     default:
         puts("zhekou3");
    }
    

    遇到死循环Ctrl + C结束

  3. 两个小算法

    • 最大公约数:如何计算最大公约数?

      常用质因数分解法、欧几里得法、相减法和辗转相减法

    • 最小公倍数

      利用最大公因数求最小公倍数,因为两个自然数的最大公因数与它们的最小公倍数的乘积,等于这两个数的积。

      // 最大公约数和最小公倍数
      #include<stdio.h>
      #include<math.h>
      
      int main(){
      	int data1, data2;
      	// data_a = 36;
      	// data_b = 36;
      	puts("请输入两个数:");
      	scanf("%d%d", &data1, &data2);
      	int data_a, data_b;
      	int data_r;
      	int data_d;
      	
      	// 使得data_a > data_b
      
      	if(data1 > data2){
      		data_a = data1;
      		data_b = data2;
      	}else{
      		data_a = data2;
      		data_b = data1;
      	}
      	
      	/*
      	// 辗转相除法
      	while(1){
      		data_r = data_a%data_b;
      		if(data_r == 0){
      			printf("最大公约数为:%d", data_b);
      			break;
      		}
      		data_a = data_b;
      		data_b = data_r;
      	}
      	*/
      	
      	// 辗转相减法
      	while(1){
      		data_d = data_a - data_b;
      		
      		if(data_d%data_b == 0){
      			printf("最大公因数为:%d\n", data_b);
      			break;
      		}
      		data_a = data_b;
      		data_b = data_d%data_b;
      	}
      	
      	// 求最小公倍数,利用最大公因数
      	int data_multy; // 最小公倍数
      	data_multy = data1*data2/data_b;
      	printf("最小公倍数为:%d", data_multy);
      	return 0;
      }
      

四、数组

  1. 数组定义

    int a[10] = {0};
    int num = 0;
    printf("地址:%p, 数据:%d", &a[num], a[num]); // &取址符
    
  2. 数组初始化

    int a[] = {1, 2, 3, 4, 5};  // 自动判断出长度为5
    int b[5] = {0}; // 初始化为0
    
  3. sizeof关键字,能计算数据的内存空间大小,也能求数组的长度

  4. 两个排序法:冒泡和简单选择排序法

    冒泡:排序len-1轮,每轮排序len-1-i次

    选择:排序len-1轮,最大的在前,从i+1排到最后一个

  5. 二维数组

    • 二维数组是特殊的一维数组,详见指针与数组章节

五、函数

  1. 函数的三要素:函数名、参数列表、返回值

  2. 函数的参数可以是:常量、变量、表达式 或者 另一个函数

  3. 函数变量生命周期:空间,被调用的时候才申请内存,调用结束立即释放

  4. 形式参数和实际参数,地址空间不同。值传递和地址传递

  5. 函数和数组:

    • 在函数的形式参数不存在数组的概念,即便传入数组定义了数组的大小也无效( 例如:int arr[10] )
    • 函数中传入的数组其实是一个地址,数组的首地址,在os(操作系统)中用8个字节表示地址
    • 通常在函数中调用某个数组时,传入数组的地址和长度 首地址:&arr[0]或 arr sizeof求长度len
    • 函数形参是数组可以写成 *int arr[] 或者 int arr 都对
    • 在C语言中,数组名是当作指针。确切的说,数组名是指向数组首元素地址的指针数组索引就是距数组首元素地址的偏移量 这是为什么C语言中的数组是从0开始计数,因为这样索引可以对应到偏移量上。
  6. 二维数组形式参数写法:a[2][3] 或者a[][3] 不能写成a[][] 能省略行号不能省略列号

  7. 二维数组,若第2维大小相等,形参的第1维可以与实参的数组不同

    // 在第2维大小相同时,形参的第1维可以与实参数组不同
    // 例如实参数组定义为
    	int scores[5][10];
    // 而形参数组定义为
    	int array[][10];
    // 或者
    	int array[20][10]; 
    // 两种方式都可,形参数组和实参数组都是由相同类型和大小的一维数组组成
    

    C语言编译系统不检查第一维的大小。学习指针后了解更深入

  8. 关于全局变量(外部变量)要注意其作用范围,只作用在其后的函数中,在变量定义之前的函数无效

    全局变量使得编程便捷,但有隐藏风险,这样所有的函数都可以操作这个变量! 少用!!

  9. 如果函数有多个返回值,可以定义全局变量来接收结果,也可以用指针(地址传递)实现多结果接收

  10. 函数调用的目的:调用者通过调用函数获得某些结果,其强调功能性的封装,既获取结果又打印结果??


六、指针

1、指针
  1. 指针==地址 &取地址运算符, *取地址中的值读出的运算符

    • *作用一:在指针变量定义或者声明的时候 标识作用
    • *作用二:取地址的运算作用
  2. 指针变量,存放地址的变量 int *p *起到了一个标识符作用,其他场景是运算符 指针变量也有自己的地址

  3. 指针是变量存放别人地址的变量,要注意类型。——6.3章节 指针变量要区分类型

    // 1.类型决定了指向的空间大小
    int a = 0x1234;
    int *p1 = &a; // *p1和*p2在取值的时候会根据指针变量的类型访问不同大小的空间
    char *p2 = &a; 
    // 2.决定指针增量的大小,增量大小和指针变量的类型有关int 增4字节,char增1字节
    ++p1;
    ++p2; 
    
  4. 使用指针的场景:

    • 地址传递,函数内实现改变 函数外的变量的值,交换两个数的值
    • 指针指向固定区域,开发过程中获取能够合法操作区域的地址获得寄存机内存的地址,进行操作
  5. 作业:输入三个数,输出按大小排序,,,※※※※※※※※※※scanf取值符

2、数组的指针变量
  1. 数组的地址

    int arr[3];
    int *p;
    p = &arr[0];
    p = arr; // 效果等同
    p+1; // p偏移sizeof(int)大小的内存
    *(p+i) == a[i]; // 等效
    
  2. 引用数组元素

    • 下标法 a[i] 对数组成员变量访问时开销大,但易理解、可读性好
    • 指针法 *(p+i) 指针访问正确的时候,指针的访问效率是远远大于数组名的访问效率
  3. 指针的偏移

    *p++; // 先对p进行取值,在对p进行++
    // 循环使用指针p的时候记得初始化 p = arr
    for(int i=0, p=arr; i<len; i++)  // 循环中的初始化条件不要写多  少些花里胡哨
    
  4. 见怪不怪:

    int arr[3];
    int *p;
    p = arr; 
    *p = *arr = arr[0]; // 都是访问第一个变量
    // ※※※※※※※※以下几种写法效果等同※※※※※※※※※
    p[i];
    *(p+i);
    *(arr+i);
    
    // ※※※※※※※指针p和数组名arr区别※※※※※※※※※:
    p++;   // ※※※※※※※※指针变量
    arr++; // ※※※※※※※※编译不过,指针常量
    
    // ※※※※※※※关于sizeof※※※※※※※※
    int arr[3];
    sizeof(arr);   // 结果12  表示整个数组的大小 3*4=12 个字节 数组3个元素,每个元素4个字节
    sizeof(p);     // 结果08  os中用8个字节表示一个地址
    sizeof(int);   // 结果04  int只占4字节
    sizeof(int *); // 结果08  os中用8个字节表示一个地址
    sizeof(char *);// 结果08  os中用8个字节表示一个地址
    
    ※※※※※※※※只要是个指针大小就为8字节,8个字节表示其地址※※※※※※※
    

    补充:

    int arr[2][3];
    
    sizeof(arr):        24  // 二维数组整个大小
    sizeof(arr[0]):     12  // 一维数组大小,每行的大小
    sizeof(arr[0][0]):   4  // 单个元素大小
    
  5. 练习题:数组翻转

3、二维数组的地址
  1. 设一个二维数组a[2][3]

    • C语言中规定,数组名代表数组首元素地址

      而a[0] a[1] a[2]是一维数组名,因此a[0]代表了一维数组a[0]中第0列元素的地址,即为&a[0][0]

      a[1] >>>> 即为 &a[1][0] a[2] >>>> 即为 &a[2][0]

    • 二维数组本质还是一个数组(数组的数组),区别于一维数组的是其数组元素还是一个数组( 子数组 )

    • a是二维数组名,也为二维数组(行、父数组)地址,a[0]、*(a+0)是一维数组(列、子数组)的地址

    • 表示数组首地址的方法:①数组名 ②&数组首元素

  2. 二维数组有关的指针

    请添加图片描述

  3. 数组指针

    • 指的是数组名的指针,即数组首元素地址的指针。即是指向数组的指针。

      int arr[2][3];
      int (*p1)[3];
      p1 = arr; // p1指向了一个整型的一维数组
      

      此时p的增量以它所指向的一维数组长度为单位

  4. 函数指针

    • 函数指针也有类型要求,必须和原函数一样

    • 定义与使用的时候要添加括号,如

      int function1(int data); // 定义函数
      int (*p2)(int data);     // 定义指针
      p2 = function1; 		 // 指向函数
      (*p2)(10); 				 // 通过指针调用函数,p2必须加括号,因为*运算符的优先级比较低
      
      int (*pcmd)(int, int);   // 可以把data1和data2变量名省略,因为函数中没用到
      						 // 形参列表强调参数的原型,参数名可省略
      
      exit(-1); // 用到stdlib.h
      

      使用函数指针在调用函数的时候和 调用变量一样 , 回调函数底层逻辑即为此

      作用:可以根据情况,不同的命令调用不同的函数

      练习题用函数指针输入1 2 3实现不同函数的调用

  5. 指针数组

    • 数组中的元素都是指针类型数据,即为指针数组

    • 指针数组是一个数组每一项都是一个指针变量

      int *p[4]; 
      // []优先级比*高,p先和[]结合,p[4]是一个数组,再与前面的*结合,表示数组为指针类型,每个元素指向一个整型变量
      
      // 区别于函数指针,这里定义指针数组的时候*p没有小括号() 
      
      // *(p[i]) 取p[i]地址里的内容,加上括号,书写习惯
      

    练习: 定义函数指针数组(数组元素为函数指针),用指针数组实现不同函数的调用

    阅读: demp_hanpointArrEx.c

    // 函数指针,一个指针
    int (*pfunc1)(int data1, int data2); 
    
    // 函数指针数组,三个指针, 名字后面加
    int (*pfunc[3])(int data1, int data2); // 定义之后要初始化,不然野指针导致段错误
    
    • 函数指针数组的调用:

      int (*pfunc[3])(int a, int b)={
       function1, 
       function2, 
       function3
      	}; // 定义
      
      // 调用
      int i = 1; 
      int ret = (*pfunc[i])(a, b); 
      

      调用的时候想想怎么定义的

  6. 指针函数

    • 返回值为指针的函数, 函数可以返回整型值、字符值、实型值等,也包括指针型数据,即地址

      int *p; // 指针变量
      int* p; // p是一个变量,是int型的指针类型,※※※※※浪着两者等同
      
      int* func(int a, int b); 
      // 解释:()优先级高,func先和()结合,显然为函数类型,函数前面有* 表示函数值是指针,int表示返回的指针指向整型变量
      

    章节6.22

    练习题:例8.25 a个学生b门课程 输入序号返回成绩 hanshupointreturn.c 注意利用返回的父数组指针与提取数据的指针之间的关系

    作业题:例8.26

  7. 二级指针

    指向 指针(地址) 的指针

    int data = 100;
    int *p = &data;
    int *pp = &p; // pp是一级指针,不能通过连续两次取值符*来获得data的内容
    
    int **p2 = &p; // p2可以访问其指向的指针指向的数据data里的内容
    **p2 和 data 等同
    

    函数里的形参之一若为 指针,实参也为指针,这就类似普通变量的值传递,不能修改主函数main里的指针的值,只是在函数中把形参指针的值修改了, 这个时候可以使用二级指针,

    int function(int **ppos){---}; // 形参写二级指针,实参写一级指针取址
    
    int main(){
     int *ppos;
     function(&ppos);
    }
    
  8. 二级指针和二维数组避坑 有关联但不等同

    • 二维数组array[3][4]的数组名虽然有**array的用法但是其本质不是二级指针
    int scores[3][4] = {0};
    int **p = scores; // 两者不等同  *p不是子数组的地址
    
    • 正确用法
    int scores[3][4] = {0};
    int (*p2)[4] = scores;
    int **p3 = &p2; // 这个时候正确 
    
  9. 总结:各种指针的应用,中小公司考题

    • 复习总结 章节6.26

七、字符串

  1. 初识

    • 通俗解释:字符串就是字符数组,双引号
    char Str1[] = {'H', 'e', 'l', 'l', 'o'}; // 法1
    char Str2[] = "Hello";  // 法2
    char *pChar = "Hello";	  // 法3
    char c = 'c'; // 注意  单字符采用单引号
    
    // 字符串初始化的时候置空
    char str3[128] = {'\0'};
    

    法2 是字符串变量,法3是字符串常量(不可修改),pChar指向了这个常量

    *pChar = 'm'; // 不可对常量进行操作
    Str[3] = 'm'; // 可以修改,结果Helmo
    

    注意指针的操作:
    可以保存地址,修改指向,指向字符串常量的地址空间
    对野指针的内存空间不能操作,上述操作

    // 接上述内容
    printf("%s", pChar); // %s格式声明,后面跟地址(指针)scanf函数也一样
    puts(pChar); 
    
    %s叫格式声明,由%和格式字符组成
    
  2. 字符串和字符数组的区别

    • 字符串比字符数组的长度多**“ 1 ”, 有结束标志’ \0 '** 字符串会自动补**\0**
    char str1[] = {'H','e','l','l','o','\0'}; 
    char str2[] = "Hello"; //两者不等同
    

    关于字符数组的赋值方法:

    1. 通过初始化,定义的时候直接复制
    2. 逐个赋值,索引访问每一个元素赋值
    3. strcpy或者strncpy函数
  3. sizeof和strlen区别 章节7.3

    sizeof用来计算数组大小,指针的大小,数据类型的大小

    strlen用来计算字符串的有效长度

  4. malloc开辟空间

    4个API的使用:

    • 空间开辟在堆区,程序跑完之后才释放(死循环可能会耗尽计算机内存),不用于栈
    • 空间使用完毕后要free,防止悬挂指针(野指针的一种)
    • realloc( p, newlen ) 扩容函数,p为指向内存的指针,newlen是要增加的长度(量)
    • *memset(int p, char c, int len) 形参:起始地址,填充内容’\0’ , 填充长度len
  5. 字符串常用操作:

    char *p = "HELAJINFF";
    char str[] = {'\0'}; 
    gets(str); // 参数都为指针
    puts(p);
    
  6. 字符串拷贝、拼接和比较 章节7.6

    tips:逻辑判断运算符 优先级高于赋值运算符=

    • 三个函数 strcpy、strcat、strcmp
    • 关于比较函数注意返回值的含义 -1 1 0
  7. assert断言

    • 包含头文件assert.h
    • 条件为假就退出

八、结构体

  1. 初识

    • 数据类型较多,需要整合,才能描述信息
    • 名字 习惯要求大写开头
    • ;分号不能忘
    • struct作为模板使用,不必赋初值,每一项不一定要使用
    • 每个成员都是结构体中的一个域 也称域表 成员列表
    • 声明的同时也可定义变量,不推荐
  2. 声明、定义和使用

    • 可以在声明的时候直接定义,也可在main中定义
    • 访问的时候采用**. 运算符**,优先级仅次于() 和 []
  3. 结构体的赋值

    • 通过大括号,在定义的时候初始化,一一对应填入

    • 通过 . 运算符访问,分别赋值

       srtuct Student stu1 = {15, 'c', "北京"}; // age、sex、addr
       srtuct Student stu2;
       stu2.age = 20;
       stu2.sex = 'f';
       strcpy(stu2.addr, "南京");		// 赋值通常是在定义的时候或者后续用strcpy函数
       
       // 赋值也可以是这种个别成员的赋值
       struct date d2 = {.month=7, .year=2024 };  //赋值内容少于变量的个数,没给的值默认为0
      
    • 结构体变量之间可以直接赋值

      struct Student stu1 = {15, 'c', "北京"};
      struct Student max;
      max = stu1; // 变量之间的赋值
      
    • 结构体和数组的结合

      int arr[3] = {5, 8 10};
      struct Student stu[3] = {{}, {}, {}}; // 看起来类似二维数组... 括号里的括号是一个结构体
      

    练习:选票系统

  4. 结构体指针

    • 任何变量都具备四要素:类型、名称、值、地址 指针存放其地址

    • 内存数据的访问:1. 变量名访问 2. 指针访问

      // 内存结构体中的数据的访问:
      p->data;		// 指针访问
      stu.data;		// 变量名访问
      
    • 应用:

      // 结构体数组的数组名  也是代表地址
      struct Student stu[10];
      struct Student *pstu;
      pstu = stu; 			// 数组名依然可以直接赋值给指针
      pstu++;					// 偏移量为一个数组元素的大小, 结合for循环打印结构体数组的内容
      
    • 指针使用tips:

      1 变量名访问成员变量使用.运算符, 指针访问只需要改为 指针名**->**
      2 指针++,指针遍历之后会到数组的尾巴,下次遍历之前要重新指向数组组头(在选票系统使用指针实现结构体初始化就有用到)

  5. 结构体数组函数综合应用

    // xm经过for循环后指向会改变,定义一个bak指针标记初始位置,或者进行加减法进行偏移
    return xm-*total;   // 函数指针的时候注意指向,
    return bak; 		// 标记内存空间初始位置,可以定义临时指针变量记录,也可对循环过后的指针变量进行偏移
    
    // 多用指针的地址传递  可以改写函数为void类型
    
  6. 结构体二级指针

    • 如果要通过函数调用来改变当前函数的局部变量的值,要对局部变量取地址指针的地址传递
    • 判断是否是二级指针:看保存的是否是指针变量的地址
  7. 联合体( 共用体 )

    • 像结构体,但是又不同,区别在空间大小,联合体取决于占用的最大的元素所占用空间 ,结构体的大小是各个元素占空间之和

    • 联合体注意其数据覆盖,同一时间只有一个成员有效

    • union Data{
          int class;
          char subject[12];
      }messi;
      // union 后面的Data可以省略
      
    • tips:cmd窗口部分乱码可能是因为函数返回类型没定义导致,指针指向越界

    • 联合体的应用,师生系统中可以定义联合体避免空间浪费

  8. 枚举类型

    • 一个变量只有几种可能的值,比如星期几,只有7种,编译器把其当常量处理

    • 枚举类型其实就是整型,作用:一是让数值看起来更直观二是将数值圈定在一个范围

    • 使用方式和结构体类似,先声明在定义,最后使用

      enum colors {red, yellow, green };
      // 创建三个常量,red的值是0,yellow的值是1, green的值是2, 定义枚举的意义是给这些常量值名字   
      // 声明枚举可以指定值,只能在声明的时候指定
      enum colors {red=1, yellow, green=5, NumCOLORS };
      // 使用枚举主要是用作定义符号量,而不是当做枚举类型来使用
      enum colors w;
      w = red;
      printf("%d", w); // 结果为w=1,NumCLORS=6,按顺序往后排
      
  9. 关键字typedef

    • 声明一个已有的数据类型的新名字。起一个新名字

      typedef int data_t;
      typydef struct{
          data_t data;
      	char name[32];
      }stu1, *stu1;
      

九、补充

  1. 运算符优先权
    请添加图片描述
  • 38
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值