C语言课程笔记-顿开莫帅

C语言基本数据类型

C语言常量

  • 什么是常量: 固定的值
  • 常量的分类:

    • 整形常量
    //整数
    //1,2,3,4  十进制数
    //特殊格式的整数
    //0123   八进制:0开头
    //08---> 不算
    //0x123  十六进制整数: 0x开头
    //0b10111 2019以下的编译器不支持
    //特殊后缀的整数
    //1u     无符号整数: 没有负数的意思
    //1L     长整形
    • 浮点常量
    //实数(包含整数包含小数)
    //1.333  默认是双精度(double)浮点型常量
    //1.33f  单精度浮点常量
    //特殊的表示方式
    //科学计数法
    //1234E-5
    //    .E-4;  错误的写法
    • 字符常量

      • 转义字符: 是具有特定含义字符,一般是由\和特殊字符组成的具有特殊含义的字符

    //字符边界符 ''
    'A'
    //一般情况只有一个字符  
    //转义字符
    //'\t'
    //'\n'
    • 字符串

      • 字符串边界符 ""
      • 每一个字符串都会存在看不见的结束符(‘\0');

C语言变量

  • 什么是变量,可以改变量(其实在C语言充当数据容器的作用)
  • 定义变量的语法: 类型 标识符;

    • 类型
    • 标识符: 就是一个名字

      • 起名字: 见名知意
      • 标识符不能用数字当做开头
      • 标识符不能是C语言中的关键字(if,case,switch,int)
      • 标识符大小是区分
    • 起名字的方式

      • 帕斯卡:大驼峰 PlayMusicByUser --->MFC
      • 驼峰 :playMusicByUser---> 使用这一种命名方式
      • 匈牙利: 特定前缀组成的命名方式

        • g_ : 全局变量 g_iNum;
        • c_: 常量 c_iNum;
    • 数据类型

      • 基本的数据类型

        • int : 整数类型
        • float: 单精度浮点类型
        • double: 双精度浮点类型
        • char: 字符类型
      • 修饰词

        • short(短)和 long(长): 体现数据占用内存(容量的大小)的基础上)
        short iNum=0;
        short int sNum=0;
        long lNum;
        long int liNum;
        //有修饰和没有修饰词是两种不同的数据类型
        //什么是用短的: 想要节省内存
        //什么是用长的: 数据超出了原来表示范围,用long
        • unsigned(无符号) 和signed(有符号)
        //无符号:表示不能表示负数
        //一般默认的类型,表示的有符号
        //年龄没有负数-->这种需求
        • const(常属性)

          • 常属性的变量定义的时候必须初始化
          • 常属性的变量不能被修改
          • 小提示: 一个程序不能大量出现常量的数--->幻数
      • 注意点:

        • short 和long 只能修饰整形
        • unsigned(无符号) 和signed(有符号) 修饰整数数和字符

    auto/extern/static (后续再说)

进制与数据存储

其他进制与进制转换

  • 十进制: 1 2 3 4
  • 二进制: 3: 0:0 1:1 10:2 11:3

    • 二进制转十进制: 短除法

  • 八进制:8: 10

    1646053931950

  • 十六进制

  • 转换的小知识: 二进制转八进制 在十六进制

    • 16:10000 8: 1000 4: 100 2:10 1:1
    • 三个二进制表示一个八进制位
    • 四二进制位一个十六进制位

  • 其他进制转十进制: 加权法

二进制与数据存储的关系

  • 二进制与存储单位

    存储单位表示含义
    bit :位一个二进制位,能表示的数字只有0和1
    byte:字节1B=8b,8个二进制位表示一个字节
    KB: 千字节1KB=1024B =2^10
    MB:兆字节1MB=1024KB=2^10KB
    GB: 吉字节1GB=1024MB=2^10MB
    TB: 太字节1TB=1024Gb=2^10GB
  • 数据的二进制存储

    • 所有数据在计算中都是用补码来存储
    • 补码: 反码+1
    • 反码: 原码取反(但是符号位不变)
    • 原码: 负数: 引入了符号位,用第一位表示整数,0:正数,1表示负数
    • 正数的二进制码 都是三码合一
    正数:3:(三码合一)   
    原码:0000 0000 0000 0000 0000 0000 0000 0011 
    反码:0000 0000 0000 0000 0000 0000 0000 0011 
    补码:0000 0000 0000 0000 0000 0000 0000 0011 
    负数:-3
    原码:1000 0000 0000 0000 0000 0000 0000 0011   //引入符号,最高位表示位
    反码:1111 1111 1111 1111 1111 1111 1111 1100   //原码取反(但是符号位不变)
    补码:1111 1111 1111 1111 1111 1111 1111 1101   //反码+1
    //3-3
    0000 0000 0000 0000 0000 0000 0000 0011    
    1111 1111 1111 1111 1111 1111 1111 1101   
    0000 0000 0000 0000 0000 0000 0000 0000        //进制位处理(寄存器有标志位处理)
    //注意点: 补码要算出十进制数,必须先转换为原码,再去用加权法
  • C语言数据的范围

范围计算: 以char类型为例

char: 1---> 0000 0000

最大值: 0111 1111

1000 0000-1

2^7-1=128-1 =127

-2^7=-128

int: 32--> -2^31-2^31-1

C语言基本输入和输出

printf函数

  • include <stdio.h> C语言标准输入输出头文件

  • printf做原样打印

    //函数
    //printf("你要打印的内容");
  • 转义字符

    常用的转义字符效果
    \n换行
    \r换行(当前行首),判断按键是否是回车键
    \t水平制表

    制表的原理: 通过控制数据的输出宽度,不足填充空格

  • 打印特殊符号

    • 通过\加上特殊符号打印符号
  • 数据的输出

    • 格式控制字符: 占位符
    int  %d
    char %c
    float %f
    double %lf
    字符串  %s
    unsigned int %u
    printf("a=%d,b=%c",1,'A');
    a=%d,b=%c
       1,  'A'
    a=1,b=A
    //一般情况: 占位符的个数是要和后面数据个数是相同
    • 浮点型的格式问题
    %+a.bf    
    %-a.blf
    a:输出数据的宽度,不足补空格
    b:小数位
    +: 右对齐 ,不写,默认是右对齐
    -: 左对齐

scanf函数

  • scanf基本用法

    • &(shif+7键按出来)取地址符
    • scanf工作原理

      • scanf("格式控制字符","变量的地址");
      • 输入的格式控制字符不需要转义字符
    • error C4996: 'scanf': This function or variable may be unsafe. Consider using scanf_s instead
    • 项目属性-->SDL改为否
  • 字符输入

    • 清除缓冲区---> 在字符或者字符串输入前 做了输入,就需要清空
  • scanf_s: vs给scanf做的增强版本

    • 输入数字类是没什么区别
    • 输入字符类或者字符串必须要加入长度
    • 其他编译器没有scanf_s 只有scanf
  • scanf一次输入多个不同类型的数据
#include <stdio.h>
int main() 
{
    //No.4 不同数据类型输入
    //数字和字符组合在一起
    int iNum;
    char xNum;
    double dNum;
    printf("input int,char,double:");
    //scanf自动区分数据,不需要认为区分
    scanf("%d %c%lf", &iNum, &xNum, &dNum);
    printf("iNum=%d,xNum=%c,dNum=%lf\n", iNum, xNum, dNum);
    printf("input int,char,double:");
    //%d,%c,%lf 
    //scanf("%d,%c,%lf", &iNum, &xNum, &dNum);
    //printf("iNum=%d,xNum=%c,dNum=%lf\n", iNum, xNum, dNum);
    scanf_s("%d %c%lf", &iNum, &xNum,1, &dNum);
    printf("iNum=%d,xNum=%c,dNum=%lf\n", iNum, xNum, dNum);

    //No.1 输入单个数据
    printf("input a num:");
    //习惯:定义变量的时候给变量初始化(赋初始值)
    int num = 0;       //定义变量赋值为0
    int result = 0;
    result=scanf("%d", &num); //等待键盘输入
    printf("num=%d\n", num);
    //No.2 输入多个数据
    int a, b;
    printf("input a,b:");
    result=scanf("%d%d", &a, &b);
    printf("a=%d,b=%d\n",a,b);
    //scanf("%d\n", &a);  没有这种学法
    float fNum;
    //scanf("%.3f", &fNum); 没有这种学法
    //No.3 字符输入
    char cNum;
    char x;
    printf("input a char:");
    //getchar();                //----> 常用
    scanf("%c", &x);            //清除缓冲区的\n x等于\n
    //fflush(stdin);            //被淘汰
    //setbuf(stdin);            //会影响文件操作
    //这个两个等效上面scanf的作用
    scanf("%c", &cNum);
    printf("cNum=%c\n", cNum);
    printf("input a char:");
    scanf("%c", &x);
    scanf_s("%c", &cNum, 1);    //长度一般用变量能够存储的数据长度
    printf("cNum=%c\n", cNum);
    //字符串--字符数组;
    return 0;
}

字符处理函数

getchar函数

  • 输入一个字符 ,getchar() 表示输入的字符
  • include <stdio.h>

putchar函数

  • 输出一个字符 ,putchar(‘A’) 要输出的字符放在括号中
  • include <stdio.h>

getch函数

  • 输入一个字符,getch() 表示输入的字符,不需要回车确认输入
  • include <conio.h>

  • 在vs中,getch()要改为 _getch()

ASCII码的知识

  • ASCII码 做整数和字符的转换

C语言运算符和表达式

运算的基本概念

  • 左值和右值以及操作数的概念

    • a=1 a:左值 1:右值

      • error C2166: 左值指定 const 对象 左值一般是变量
      • 右值: 没有什么太多要求
    • 操作:=需要两个数字运算符,操作数2
    • 单目,双目, 三目
  • 优先级:算法: 算表达式的顺序(先算*/ 再算+ -)
  • 结合性: 读法: a=1; 正确应该是把1赋值给a

逗号-->赋值-->逻辑-->条件-->算术-->位-->括号

基本运算符

  • 赋值运算符
a=1;
  • 算术运算符
//+ -  
//* /                   在写表达式的时候乘法不能省略
//2(3+4)
int a=2*(3+4);        // 在写表达式的时候乘法不能省略
//除法
1/3=0;                //当除法的两遍都是整数的时候,会自动取整
//1/2+2/3+3/4;
double result=1/2.0+2/3.0+3/4.0
//取余:%
int num=1%3         //1  x%n  [0,n-1]--->随机数
//余数的符号
num=1%-2;            //1 余数只和被取余数的正负有关
  • 复合赋值运算符
int a=1;
a+=2;    //a=a+2
a/=2;    //a=a/2
a*=2;    //a=a*2
a=2;
a*=1+2*3; //表达式右边有没有括号,都没关系,都是一个整体
//a=a*(1+2*3); //14
  • 条件运算符

    • 条件表达式只有两个结果: 0(表示不成立) 或者 1(成立)
    • 在计算机中非零值表示成立,只有0(\0)或者NULL 表示不成立
    • 不存在连续操作,例如描述 a大于1并且小于2 1<a<2 错误

      • 1<a<2是永远成立的 1<a 结果[0,1] , [0,1]<2 永远成立
//> < 
//不等于 !=  中间没空格,有空格就是错误
//等于 ==
//>=  <=
  • 逻辑运算符
&&: 逻辑与运算--->并且
||:逻辑或运算--->或者
!: 否定      --->成立变成不成立,不成立变成成立
//!3  --> 0
//!0  --> 1
aba&&ba\\b
0000
1001
1111
0101

综上:

a&&b : a和b都成立它才能成立 ,其他情况都是不成立

a||b : a和b都不成立它才能不成立 ,其他情况都是成立

逻辑与运算和逻辑或运算都会存在短路现象(计算机偷懒现象)

int a = 2;
a > 1 || (a = 4);  //    a > 1成立 所以a > 1 || (a = 4)成立
a < 1 && (a = 5);  //   a < 1不成立 所以a < 1 && (a = 5)不成立
printf("%d\n", a);

位运算符

目前: 学会运算符方式即可

&: 按位与
|: 安位或运算
~: 按位取反
^: 异或
>>:左移
<<: 右移

左移: 正负数都是右边补0

右移: 正数: 左边补0 负数: 左边补1 不要去纠结为什么,就是一个前辈发现的计算规则,如此而已

特殊运算符

  • ++ --
int a=1;
a++;   //后置   先其他运算,改变自己
++a;   //前置   先改变自己,在做运算
//a++  a=a+1;
a--;
--a;
//a--  a=a-1
int a = 1;
int b = 1;
int result;
result = a++;  //result=a ,在a=a+1
printf("result=%d\ta=%d\n", result, a);  //1 2
result = ++b;  //先b=b+1  在做 result=b
printf("result=%d\tb=%d\n", result, b);  //2 2
a = 1;
result = 3 * a++;
printf("result=%d\ta=%d\n", result, a);
  • sizeof

    • 统计类型占用内存(字节)
    • 他是再编译期完成的
  • ?:
表达式1?表达式2:表达式3
1成立  2
1不成立  3
  • 逗号运算符

    • 有效值 是最右边的值

内存共享问题

#include <stdio.h>
int main() 
{
    int a = 1;
    int resultFirst = a++ + a++ + ++a + ++a;  //a:3
    //只看前置,不看后置,再看几部分
    // 3+3+3+3
    //resultFirst:3*4=12  a:5
    //只看前置:a++ + a++ + ++a + ++a 两个前置:a要加两次,运算时a的值3
    //再看几部分:a++ + a++ + ++a + ++a 有四部分: 4
    printf("result=%d\ta=%d\n", resultFirst, a); //12 5
    int b = 1;
    int resultSecond = b++ * b++ * ++b * b++ * b++;
    //自己会算就可以
    //b运算时候: b=2
    //resultSecond:2^5;
    printf("result=%d\tb=%d\n", resultSecond, b); //32 6
    int c = 1;
    int resultThree = c++ * c-- * c++ * ++c * --c * c++;
    printf("result=%d\tc=%d\n", resultThree,c);
    //切记: 不要自己再自己的代码里面写这样的东西
    return 0;
}

C语言分支结构

if语句

if(表达式)   //如果
{
    //满足条件执行
}    //尽量带上这个{},不带只管理一条语句
//No.1 基本if语句
    if ("很帅")            //非零表示成立
    {
        printf("你要给生猴子!\n");
    }
    int a = 1;
    if (a = 0)   //赋值语句充当条件,最终是赋的值充当条件 if(0)
    {
        printf("赋值语句!\n");
    }
    //求最大值:
    int b;
    int c;
    scanf_s("%d%d%d", &a, &b, &c);
    //No.2 if处理最大值问题
    int max = a;
    if (max < b)
    {
        max = b;
    }
    if (max < c) 
    {
        max = c;
    }
    printf("Max=%d\n", max);
    //No.3 if处理多个条件使用逻辑运算符&& ||
    //判断一个数字是否能够被3和5整除
    int num = 45;
    if (num % 3 == 0 && num % 5 == 0) 
    {
        printf("满足条件的数字\n");
    }
    //判断一个数字是否能够被3但是不能被5整除
    //num % 3 == 0 && num % 5 != 0
    //判断一个数字能够被3或者被5整除
    //num % 3 == 0 || num % 5 == 0

if_else语句

//复合语句
if(表达式)
{
    //条件成立执行这里  
    printf("1");
}
else
{
    //条件不成立执行这里
    printf("1");
}

if_else if _else

//条件细化
//条件的逐步筛选
if(表达式1)
{
    //1.
}
else if(表达式2)
{
    //2.
}
else if(表达式3)
{
    //3.    
}
.....
else
{
    //
}
//整个语句一条复合语句,只会做一次成功的比较,执行一个地方

switch语句

switch(表达式1)
{
    case 常量1:  //表达式1 和常量1比较
        语句1;
    case 常量2:
         语句2;
    case 常量3:
         语句3;
    default:
         其他情况执行的地方;    
}
//1.switch执行过程
//2.case后面必须是常量,一般写的数字,或者字符,不能写字符串
//3.switch语句只做一次成功的比较,后面的语句不需要比较,都会一次执行
//4.default的位置是随便放的,一般是放在最小面的
//5.switch语句不执行case和default以外的其他语句

switch常用形态

菜单跳转

//1.菜单交互
    while (1) 
    {
        //菜单绘制
        printf("------【xxx管理通】------\n");
        printf("\t0.退出系统\n");
        printf("\t1.录入信息\n");
        printf("\t2.删除信息\n");
        printf("\t3.查找信息\n");
        printf("-------------------------\n");
        //菜单交互
        int key = 0;
        scanf_s("%d", &key);
        switch (key) 
        {
        case 0:
            printf("退出系统成功!\n");
            system("pause");        //防止闪屏 ,等待按键继续
            exit(0);        //关闭整个程序;
            break;
        case 1:
            printf("---录入信息!\n");
            break;
        case 2:
            printf("---删除信息!\n");
            break;
        case 3:
            printf("---查找信息!\n");
            break;
        default:
            printf("---输入错误,重新输入\n");
            break;
        }
        system("pause");        //防止闪屏 ,等待按键继续
        system("cls");            //清屏
    }

按键交互

#include <stdio.h>
#include <stdlib.h>        //system函数头文件
#include <conio.h>
int main() 
{
    while (1) 
    {
        printf("貌美的如花小姐姐朝你走来!---\n");
        char userkey = _getch();  //不可见的按键交互,不需要回车确认输入
        switch (userkey) 
        {
            //相同的case处理方案可以把所有case写在一起
        case 'w':
        case 'W':
        case 72:
            printf("你朝-->上方-->逃逸\n");
            break;
        case 'A':
        case 'a':
        case 75:    
            printf("你朝-->左边-->逃逸\n");
            break;
        case 's':
        case 'S':
        case 80:
            printf("你朝-->下方-->逃逸\n");
            break;
        case 'd':
        case 'D':
        case 77:
            printf("你朝-->右边-->逃逸\n");
            break;
        }
    }
    return 0;
}

变量的作用域和生命周期

  • 作用域: 使用范围

    • 局部变量
    • 全局变量
    • 外部变量:extern
  • 生命周期: 变量产生到死亡的时间段

    • 静态变量
    • 自动变量(auto)
#include <stdio.h>
int g_num;                    //全局变量
extern int e_num;            //告诉当前文件,该变量是外部文件中的变量
//全局变量没有做初始化,默认0
//全局变量不易过多,因为生命周期,只有当程序关闭 才结束
int main() 
{
    //静态变量养成初始化的习惯
    static  int sNum=1;        
    //会存放上一次执行结果,初始化操作只会做一次
    printf("%d\n", sNum);
    while (1) 
    {
        static  int sCount = 1;   //只会执行
        int count = 1;              //每一次循环都执行
        sCount++;
        count++;
        printf("sCount=%d,count=%d\n", sCount,count);
    }
    printf("%d\n", g_num);
    printf("%d\n", e_num);
    int iNum = 0;                //局部变量
    {
        int number = 0;            //局部变量
        printf("%d\n", number);
    }
    int num;
    //error C4700: 使用了未初始化的局部变量“num”
    //printf("%d\n", num);        

    //printf("%d\n", number);  未定义的标识符
    for (int i;0;) 
    {
    }
    //i = 3;  //未定义

    return 0;
}

C语言循环结构

基本循环

  • while循环
  • do-while循环
  • for循环
while(表达式)
{
     //循环体    
    //.....
    //循环条件的改变-->朝着不满足条件方向走
}
//无论条件成立与否,至少执行一次
do
{
    
}while(表达式);
//for用的多一点
for(表达式1;表达式2;表达式3)
{
    循环体4;    
}
//1  2 4 3  2 4 3  2 4 3
//表达式3写是循环变量的改变
//表达式2:循环条件
//表达式1:循环条件初始化
//--表达式1定义的变量只能在循环中使用

特殊形态

while(1);   //死循环-->一直在循环
while(getchar()!='\n');
do{}while(0);
for(;;)

循环跳转语句

break;  跳出整个复合语句
continue: 跳出本次,继续复合语句
goto: 跳转   --->深层循环嵌套的跳出
(千万不要有用goto写这种荒谬的想法)

循环嵌套

#include <stdio.h>
int main() 
{
    //No.1行列关系描述
    //for循环嵌套,原则上来说或任何循环都可以随便嵌套
    for (int i = 0; i < 4; i++)            //i=0    -------->行的变化
    {                                    //
        for (int j = 0; j < 3; j++)     //j=0  j=1  j=2 --->列的变化
        {                                //
            printf("%d%d\t", i, j);        //00   01   02
        }                                //
        printf("\n");
    }
    //00 01 02 \n
    //10 11 12 \n
    //20 21 22 \n
    //30 31 32 \n
    /*
            i   j
    ****    1   4    5-i
    ***     2   3    5-i
    **      3   2    5-i
    *        4   1
    
    */
    for (int i = 1; i <= 4; i++) 
    {
        for (int j = 1; j <= 5 - i; j++) 
        {
            printf("*");
        }
        printf("\n");    //换行才有行列关系
    }
    /*      i   j
    *       1   1  2*i-1
   ***      2   3  2*i-1
  *****     3   5  2*i-1
 *******    4   7  2*i-1
    */
    for (int i = 1; i <= 4; i++)
    {
        for (int j = 1; j <= 5 - i; j++)
        {
            printf(" ");
        }
        for (int k = 1; k <= 2 * i - 1; k++) 
        {
            printf("*");
        }
        printf("\n");    //换行才有行列关系
    }
    //No.2 遍历做二次筛选
    //-->给你一个范围
    //-->给你条件
    //100-999  abc  a^3+b^3+c^3=abc;
    //100-999里面的求素数: 只能被1和自身整除
    for (int i = 100; i <= 999; i++)  //每次循环从区间里面拿一个数字出来
    {
        int flag = 1;                  //每个数字的标记都是从1开始
        for (int k = 2; k < i; k++)   //i=100
        {
            if (i % k == 0)              //100%2==0
            {
                flag = 0;    //设置一个标记位  //flag=0
                break;
            }
        }
        //退出里面循环有两种情况: 
        //第一种,里面if执行了(flag=0)
        //第二种里面一次都没执行(flag=1)
        if (flag == 1)                        
        {
            printf("%d\t", i);
        }
    }
    return 0;
}

C语言一维数组

认识数组

  • 如何创建一个数组
//No.1 数组定义语法
//类型 数组名[数组长度]
int  num[3];
float fNum[3];
double dNum[3];
char cNum[3];
//类型: 学过的数据类型
//数组名: 随便起的
//数组长度:根据需求决定数组长度
  • 数组的长相(在内存上的长相)

    • 数组是什么? 多个变量名有规律并且内存连续的变量的集合(白话:多个普通变量)

  • 数组初始化

    //No.1 创建数组的时候赋值,数组长度和元素个数相同
    int array[3]={1,2,3};   //{}不能省略,arary[0]=1 array[1]=2 array[2]=3
    //No.2 元素个数和长度不同,默认初始化为0
    int array2[3]={1};      //arary2[0]=1 array2[1]=0 array2[2]=0
    //No.3 当初始化的元素完整,数组长度可以省略
    int array3[]={1,2,3};    //自动推断数组长度为3
    //No.4 不存在数组长度为0的数组
    //int array4[];    错误
    //No.5 不能先创建数组在使用{}方式初始化
    //int array[4];
    //array[4]={1,2,3,4};    错误的写法
  • 数组的访问

    • 数组名[下标]
    • 一般操作数组都是用循环操作(输入还是打印)

一维数组常规操作

  • 插入---有序插入
#include <stdio.h>
int main() 
{
    int array[100];                //数组能存储的最大元素个数是100个
    int curSize = 0;            //当前元素是0
    int data = 0;
    while (1) 
    {
        printf("input data:");
        scanf_s("%d", &data);
        array[curSize++] = data;  //array[0]=1 curSize=1  
        //做调整
        //在数组里面做下标-1操作或者+1操作,需要考虑下标合理性
        for (int pos = curSize - 1; pos > 0; pos--) 
        {
            if (array[pos] < array[pos - 1]) 
            { 
                int temp = array[pos];
                array[pos] = array[pos - 1];  //0-1: -1
                array[pos - 1] = temp;
            }
            else 
            {
                break;
            }
        }
        int user = 0;
        while (getchar() != '\n');
        user = getchar();
        if (user == 'n' || user == 'N')  //输入n和N退出 输入状态
            break;
    }
    for (int i = 0; i < curSize; i++) 
    {
        printf("%d\t", array[i]);
    }
    printf("\n");
    return 0;
}
  • 查找+删除(移位)
#include <stdio.h>
int main() 
{
    //查找和删除
    int array[10] = { 10,20,30,40,50,60,70,80,90,100 };
    int curSize = 10;
    //数组的删除叫做伪删除
    int data = 60;            //要找的数据
    //数组的查找
    int pos = -1;            //查找下标一般用-1 ,数组下标会是0
    for (int i = 0; i < curSize; i++) 
    {
        if (array[i] == data) 
        {
            pos = i;       //记录找到元素下标
            break;
        }
    }
    if (pos == -1) 
    {
        printf("没有找到相关元素,无法删除!");
    }
    else 
    {
        //在数组里面做下标-1操作或者+1操作,需要考虑下标合理性
        for (int k = pos; k < curSize-1; k++)   //k=9
        {
            array[k] = array[k + 1];            //array[10]
        }
        curSize--;            //数组真正的删除
    }
    for (int i = 0; i < curSize; i++) 
    {
        printf("%d\t", array[i]);
    }
    printf("\n");
    return 0;
}
  • 查找+修改
#include <stdio.h>
int main() 
{
    //查找和修改
    int array[10] = { 10,20,30,40,50,60,70,80,90,100 };
    int curSize = 10;                                    //记录当前数组中元素个数
    int data = 60;
    int pos = -1;
    for (int i = 0; i < curSize; i++) 
    {
        if (array[i] == data) 
        {
            pos = i;
            break;
        }
    }
    if (pos == -1) 
    {
        printf("没有找到无法修改!\n");
    }
    else 
    {
        printf("请输入新的值:");
        scanf_s("%d", &array[pos]);
    }
    for (int i = 0; i < curSize; i++) 
    {
        printf("%d\t", array[i]);
    }
    
    return 0;
}
  • 排序--->冒泡排序
#include <stdio.h>
int main() 
{
    //冒泡排序
    int array[10] = { 60,70,80,90,100,10,20,30,40,50 };
    int curSize = 10;
    //排序
    //int count = 0;
    for (int i = 0; i < curSize; i++)          //每个元素都需要一个机会冒泡
    {
        for (int j = 0; j < curSize-1-i; j++)  //描述每个元素冒泡过程
        {
            //从小到大排序
            if (array[j] > array[j + 1])  //不满足规则交换
            {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
                //count++;
            }
        }
    }
    for (int i = 0; i < curSize; i++) 
    {
        printf("%d\t", array[i]);
    }    
    return 0;
}

字符数组和字符串

  • 字符数组和数字类数字处理方案相同的
  • 字符串: 存在一个不可见字符\0
  • 字符串的打印: %s去做打印

    • %s的工作原理: 从首地址打印到\0 所以%s对应的是地址
//No.1 字符数组
    char array[] = { 'A','B','C' };  //自动推断长度3
    for (int i = 0; i < 3; i++) 
    {
        putchar(array[i]);
    }
    putchar('\n');
    //No.2 字符数组操作字符串
    //字符串有一个不可见字符\0
    char str[] = "ILoveyou";         //自动长度是:9
    char inputstr[] = { 'A','B','C','D','\0','A','B','C' };
    //%s对应的是地址
    printf("%s\n", &inputstr[0]);    //ABCD  &inputstr[0]到\0
    printf("%s\n", &inputstr[2]);     //CD    &inputstr[2]到\0
    printf("%s\n", &array[0]);         //ABC烫烫烫烫烫烫烫烫烫烫烫烫烫烫?
    //    0    1   2   3    4   5  6   7
    //{ 'A','B','C','D','\0','A','B','C' }
    // 数组名代表第一个元素的地址
    char output[] = "ILoveyou";
    printf("%s\n", output);   //&output[0]
    int key = 0;
    scanf_s("%d", &key);
    while (getchar() != '\n');        //字符串做输入前做了输入,可以清除缓冲区
    char username[10];
    printf("请输入用户名:");
    //scanf不需要加长度,增强版要加长度 
    //scanf输入不接受带空格的输入
    scanf_s("%s", username,10);  
    printf("name:%s\n", username);
    //字符串输入函数: gets:输入+puts:打印
    char passw[20];
    //所有字符串处理用的数组
    printf("输入密码:");
    while (getchar() != '\n');
    gets_s(passw, 20);        //20一般用的是passw的长度
    puts(passw);            //自带换行
    printf("-----\n");

字符串处理函数

  • strcat: 连接
  • strcpy:拷贝
  • strcmp:比较
  • strlen:统计可见长度

C语言二维数组

认识二维数组

  • 如何创建二维数组
//NO.1 如何创建二维数组,固定语法: 类型 数组名[数组长度1][数组长度2];
int array[2][2]; 
//数组长度1: 2-->行
//数组长度2: 2-->列
//总元素个数:数组长度1*数组长度2
//最大下标是: 数组名[数组长度1-1][数组长度2-1]   array[1][1]
  • 在内存上的长相

常规操作

  • 数组的初始化
  • 数组的遍历
//No.1 完整初始化
int array1[2][2]={1,2,3,4};
//No.2 默认初始化
int array2[2][2]={1,2};
//No.3 带{}默认初始化 ,一个{}代表一行
int array3[3][4]={{1},{2},{3}};   //第一行第一个元素:1,第二个第一个元素:2,第三行第一个元素:3
//No.4 数据完整初始化,数组长度1可以不写
int array[][3]={1,2,3,4,5,6,7,8,9}; //自动推导数组长度1是:3
  • 二维数组的基本操作

    • 行列数据的操作(excel表数据操作)

      • 行求和
      • 列求和
    • 二维数组充当地图
    • 矩阵变化
  • 二维数组操作字符串

    • 初始化
    • 遍历
    • 多个字符串排序

C语言函数

认识函数

//No.1 学会创建函数
函数返回值类型  函数名(函数参数)
{
    //函数体
    return  函数返回值;
}
//没有返回值写: void :没有
//没有参数可以不写
void  函数名()  //void 函数名(void)
{
    //没有返回值不代表没有return
    return ;    //return可以不写,没有返回值可以写
    //可以结合if语句提前结束函数
    printf("永远不会执行");    
}
//函数返回值类型: 是由return后面值的类型决定
int ReturnInt()
{
    return 1;    
}
double ReturnDouble()
{
    double d=1.11;
    return d;
}
char ReturnChar()
{
    return 'A';    
}
//函数名: 和标识符其名的规则一样
//函数参数:
//形参--->创建函数时用的参数(类型修饰的)
//实参--->调用函数时用的参数(没有类型修饰)
int Max(int a,int b)        //形参 int a,int b //int a=1 ,int b=2
{
    return a>b?a:b;
}
//调用函数: 函数名(实参);     //形参和实参类型必须一致
Max(1,2);            //1,2实参
int a=1,b=3;
Max(a,b);            //a和b叫做实参
//函数调用每一次使用的内存都是不一样。
//所有传参的方式都是用赋值的方式传参  Max(1,2)
#include <stdio.h>
//No.1 无参,无返回的函数,用来做效果,
//这种效果一般可能需要重复使用
void print() 
{
    //return;  函数遇到这个reutrn就结束
    printf("----------------------\n");
    printf("\t0.退出\n");
    printf("\t1.录入\n");
    printf("\t2.浏览\n");
    printf("----------------------\n");
}
//No.2 带返回值 带参
//用来做数据的加工
//函数处理后结果需要在调用者中使用,可以同函数返回值体现
int Max(int a, int b)    //int a=aa,int b=bb
{
    return a > b ? a : b;//2
}
//拷贝本的问题
void modifyNum(int a)   //int a=num;
{
    a = 1004;            //局部的
    printf("%d\n", a);
}
void printA() 
{
    //a = 0;
}
int main() 
{
    print();  //函数名(参数)        
    print();
    int aa = 1;
    int bb = 2;
    int result=Max(aa, bb);    //函数返回值是函数调用表达式代表值
    //Max(aa, bb) = 101;    //函数返回值是一个值,不能充当左值
    printf("Max=%d\n", result);
    printf("Max=%d\n", Max(3, 4));    //Max(3, 4)是一个int的值,所以直接打印
    printf("Max=%d\n", Max(Max(5, 6), Max(4, 3)));
    //上面一行等效下面三行
    int first = Max(5, 6);
    int second = Max(4, 3);
    printf("Max=%d\n", Max(first, second));

    //所有传参方式都是赋值方式
    int num = 100;
    modifyNum(num);
    printf("num=%d\n", num);
    //printf("a=%d\n", a);
    return 0;
}

函数传参

#include <stdio.h>
#include <string.h>
//No.1 对于函数的每一个参数自己都要能解释清除
int Max(int a, int b)    //功能:求最大值, 求谁的最大值(a和b的最大)
{
    return a > b ? a : b;
}
//No.2 传一维数组
//数字类的数组必须要传入数组长度
void printArray(int num[], int arrayNum) 
{
    for (int i = 0; i < arrayNum; i++) 
    {
        printf("%d\t", num[i]);
    }
    printf("\n");
}
//void printArray2(int* num, int arrayNum); 和上面写法等效
//字符类的不需要长度 ,字符串结束标记
//strlen
int my_strlen(char str[])   //str="ABC"
{
    int count = 0;
    while (str[count] != '\0')    //str[0]:'A'  str[1]:'B' str[2]:'C'  str[3]:'\0'
        count++;                //count=1     count=2    count=3
    return count;                //count=3
}
//int my_strlne(const char* str);   //const常属性
//No.3 函数调用的时候,形参和实参类型必须一致
//不一致会存在强制类型转换 ,如果转换不了直接崩掉
void print(int num)  //int num='A';  //int num="ILoveyou";
{
    printf("\nnum=%d\n", num);
}
//No.4 二维数组独有传参方式
//行可以不同,但是列必须相同
void printArray2D(int array[][4], int row, int cols) 
{
    for (int i = 0; i < row; i++) 
    {
        for (int j = 0; j < cols; j++) 
        {
            printf("%d\t", array[i][j]);
        }
        printf("\n");
    }
}
int main() 
{
    Max(3, 4);            //赋值的方式传参
    int array[3] = { 1,2,3 };
    //写一个在函数打印数组
    printArray(array, 3);
    int array2[5] = { 1,2,3,4,5 };
    printArray(array2, 5);
    char str[] = { "ILoveyou" };
    printf("%d\t", my_strlen("ABC"));
    printf("%d\t", my_strlen(str));
    printf("%zd\t", strlen(str));        //zd  ---unsigned int 
    print('A');
    //print("ILoveyou");
    // 希望大家把编译这种提醒当做错误
    // warning C4047: “函数”:“int”与“char [9]”的间接级别不同
    // warning C4024: “print”: 形参和实参 1 的类型不同
    int array2D[3][4] = { {1},{2},{3} };   //其他的值默认初始化为0
    printArray2D(array2D, 3, 4);
    //所有的数组为参数,都是传入数组名

    return 0;
}

函数调用

  • 普通调用: 函数名(实参) ,保持形参和实参的类型和数目一致即可
  • 嵌套调用

    • 成为一个函数参数
    • 自己调用自身(递归调用)

      • 退出性条件
      • 推导公式

标准库中一些函数

  • printf和scanf返回值
  • 随机函数

C语言指针基础篇

什么是指针

指针就是一个地址,如何获取地址?用的是&:取地址符

  • 指针就是一个整数
  • 获取指针:&

指针变量

存放地址的,也就是存放一个特定的整数(这个的整数可以表示地址)

  • 如何产生一个指针变量

​ 类型* 变量名;

​ 类名 *变量名;

  • 指针变量的两个重要概念

    • 指针的类型: 去掉变量名
    • 指针所指向的类型:去掉变量名和*号

​ 用指针的时候需要保持上述两个类型的一致

int* p;
//类型: int*
//所指向的类型: int  --->本质就是指针所操作数据类型
int(*pp)[3];  //--->指针
//int(*)[3];
//int[3]--->pp操作的就是一个数组
int* pArray[3];//--->数组
//上面两个本节课不需要考虑如何使用,下节课在做研究
  • 不同类型的指针变量

    • 所有类型指针占用的字节数都是相同 32位系统下都是4个字节
    • 特殊的指针类型: void*
    • 专门用来初始化指针变量的东西:NULL

      • 防止悬浮指针
      • 防止野指针
    • 指针变量,获取当前地址中值: *指针变量

指针的运算

  • *指针变量: 指针变量所指向的内存存储的值
  • p+n或者p-n操作:实质是内存字节偏移,和指向内存存储的数据类型有关

    • p+sizeof(指针所指向的类型)*n
    • 对于一个指针变量来说,不会单独的去偏移,一般要指向一段内存做偏移(数组)
    • 对于不同类型的指针之间是没有什么算术运算
    • p++ 和p-- 也算p+n

内存四区

#include <stdio.h>

void print() 
{
    static int num = 1;        //这个代码运行的时候只执行一次
    //不做初始化默认为0
    num++;
    printf("%d\n", num);
}

char* returnPoint() 
{
    //返回局部变量地址,不允许的
    //函数调用完,内存会被系统回收(清除所有数据)
    char array[10] = "ILoveyou";
    //%s 打印方式,从首地址开始,打印到\0
    char* p = &array[0];
    //把数据存到堆区,返回堆区这段内存的首地址
    return p;
}
int main() 
{
    print();        //num=2;
    print();        //num=3;
    //num = 3;        //错误静态变量有作用
    int* p = NULL;    //0存放在常量区
    //*p = 1234;        //不能修改0所在的内存   写入访问权限冲突
    //printf("%d", *p);    
    //------------------------------------------------------
    char* str = "ILoveyou";  //解析: 把这段内存的首地址赋值给指针变量
    char* pchar;
    puts(str);
    pchar = "ILoveyou";
    //*pchar = 'M';           //写入访问权限冲突。
    puts(pchar);
    //-------------------------------------------------------
    char array[10] = "ILoveyou";
    pchar = &array[0];
    *pchar = 'M';
    puts(array);

    int* result = returnPoint();
    puts(result);
    puts(result);
    puts(result);
    puts(result);
    return 0;
}

万能指针

  • 万能指针就是void* 类型的指针变量
  • 万能指针在访问数据的时候必须做强制类型类型
#include <stdio.h>
int main() 
{
    int num = 10;
    void* pVoid = &num;    //解析方式: pVoid=&num; 不是*pVoid=&num;
    printf("%d\n", *(int*)pVoid);
    double dNum = 1.11;
    pVoid = &dNum;
    printf("%.3lf\n", *(double*)pVoid);
    //万能指针使用的时候要强制转换为目标类型(就指向数据类型的指针)
    int number = 0x00410042; //高低字节:左边:高  右边:低
    printf("%d\n", number);
    void* p = &number;
    char* pp = (char*)p;
    //两位十六进制位是不是一个字节
    //一个十六进制位是4个二进制位
    //8个二进制位是不是一个字节  8个二进制位是不是2个十进制数
    //42 00 41 00
    printf("%c\n", *pp);    //42 -->B    //0000005B9FDBF5E4  低地址
    char* pc = (char*)p;        
    printf("%c\n", *(pc + 2));//41-->A    //000005B9FDBF5E6   高地址
    // -->小端模式  高字节存放到内存地址高的地址上
    // -->大端模式  高字节存放到内存地址低的地址上
    //十进制:1235
    //1高位 5低位
    printf("%p\n", pp);        //0000005B9FDBF5E4
    printf("%p\n", pc + 2);    //0000005B9FDBF5E6
    //万能指针应用: 统一接口(统一函数传参)--->目前不需要大家掌握
    //malloc(void* p,int arrayNum); 会使用这样的函数即可,不需要自己会写
    //数据类型在讲数据类型讲过
    return 0;
}

C语言指针与数组

指针操作数组

  • 指针操作一维数组
//int array[3]
//数组名: 代表了这段内存首地址  --->指针
//&array[0]
//正常操作: 直接存储数组的首地址
//直接把指针当作数组名就可以了
//非正常操作:指向其他元素开始操作数组

#include <stdio.h>
int main() 
{
    int array[3] = { 1,2,3 };
    printf("%p\n", array);
    printf("%p\n", &array[0]);
    int* p = array;
    for (int i = 0; i < 3; i++) 
    {
        //printf("%d\t", p[i]);  //--->这种方式使用不会错误
        printf("%d\t", *(p + i));
        //*(p+i) 等效p[i];
    }
    printf("\n");
    //array = array + 3;  不能修改
    //array-->&array[0]
    //array+3-->&array[3];
    for (p = array; p < array + 3; p++) 
    {
        printf("%d\t", *p);
        //*p等效p[0]  取当前地址对应内存中值
    }
    printf("\n");

    int test[3] = { 1,2,3 };
    int* pT = &test[2];
    printf("%d\n", pT[0]);   //指针操作数组,用数组用法,[0]下表不一定是第0个元素
    printf("%d\n", pT[-1]);
    printf("%d\n", pT[1]);     //未知量  -858993460
    return 0;
}
//一级指针操作字符串
    char str[] = "ILoveyou";
    char* pstr = str + 3;
    puts(pstr);
    //[]操作等效取*运算
    putchar(pstr[0]);   //pstr[0] 等效*pstr操作,等效取当前地址下面值
    //*pstr 等效*(str+3) 等效 str[3];
    char* lastchar = str + 8;
    putchar(lastchar[0]);
    pstr = str;
    printf("\n");
    puts(pstr);
  • 指针操作二维数组
//二维数组基础知识
//数组名:指针数组的指针
//行地址:数组名[i];
//一级指针操作二维数组 (二维数组在内存连续的)
//数组指针操作二维数组
#include <stdio.h>
//void printArray2D(int p[][3], int cols, int row) 
void printArray2D(int(*p)[3], int cols, int row) 
{
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%d\t", p[i][j]);   //自己写代码这样就行,怎么方便怎么来
        }
        printf("\n");
    }
}

int main()
{
    int array[2][3] = { 1,2,3,4,5,6 };
    int* p = &array[0][0];
    //warning C4047: “=”:“int *”与“int (*)[3]”的间接级别不同
    //p = array;
    printf("%p\n", array);
    printf("%p\n", &array[0][0]);
    printf("%p\n", array[0]);

    printf("%p\n", array + 1);   //12/4=3
    printf("%p\n", p + 1);         //4
    printf("%p\n", array[0] + 1);
    //char* pC = &array[0][0];
    //int* pI = &array[0][0];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%d\t", *(p + j + i * 3));
            //j+i*3 把坐标转为第几个元素
            //p+n 等下偏移
        }
        printf("\n");
    }

    int(*pArray)[3] = NULL;    //括号必须要有,列数和表示的数组的列数必须相同
    //先算() ,他是一个子指针, 数组指针  指向数组的指针
    //联想整形指针,指向整数
    pArray = array;
    //直接当作数组名去名
    printf("-------------------------------\n");
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%d\t", pArray[i][j]);   //自己写代码这样就行,怎么方便怎么来
        }
        printf("\n");
    }
    printf("-------------------------------\n");
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%d\t", *(*(pArray + i) + j));
            //一维数组:p[i] 等效 *(p+i)
            //printf("%d\t", *(pArray[i] + j));
            //取* 等效数组下表运算 *(pArray+i)  (pArray+i)[0]
            //printf("%d\t", *((pArray + i)[0] + j));
            //printf("%d\t", ((pArray + i)[0] + j)[0]);
            //printf("%d\t", (pArray[i] + j)[0]);
            //看到会读就OK
        }
        printf("\n");
    }
    printf("-------------------------------\n");
    printArray2D(array, 2, 3);

    //存储多个字符串--->指针数组 多个指针变量
    //整形数组: 多个整形变量
    char* pstr[3] = { "Iloveyou","IMiss","IiIIII" };  //每一行上面字符串可以不相同,并且不浪费
    //int array[3]  array[0] array[1] array[2]
    //pstr[0] pstr[1] pstr[2]; 分别存储的是每个字符串首地址
    for (int i = 0; i < 3; i++) 
    {
        puts(pstr[i]);
    }
    //字符串二维数组也可以存储多个字符串? 列数相同 会导致浪费,多余内存
    return 0;
}

二级指针

//int** p=NULL;
//int*** pp=NULL;
#include <stdio.h>
int main() 
{
    int a = 1;
    int* p = &a;        //一级指针变量存储的普通变量的地址
    int** pp=&p;        //二级指针变量存储一级指针变量地址
    //*地址 --->得到当前地址对应内存中的值
    printf("%d\t%d\n", *p, p[0]);
    printf("%d\t%d\n", **pp, pp[0][0]);
    int array[2][3] = { 1,2,3,4,5,6 };
    //一级指针算偏移
    //数组指针做遍历
    // warning C4047: “=”:“int **”与“int (*)[3]”的间接级别不同
    // 所有这种提醒麻烦大家当作处理 C++当作是错误的
    //pp = array;
    //for (int i = 0; i < 2; i++) 
    //{
    //    for (int j = 0; j < 3; j++) 
    //    {
    //        printf("%d\n", pp[i][j]);
    //    }
    //    printf("\n");
    //}
    return 0;
}

动态内存申请

  • 动态内存申请的函数
//No.1 断言
#include <assert.h>
assert(p);        //p等于 触发断点,中断程序 断言

#include <malloc.h>
//#include <stdlib.h>
//No.2 malloc  动态内存,不做初始化
void*  malloc(size_t _Size);  //size:申请内存的总字节数
//No.3 calloc  动态内存申请,会初始化为0
void*  calloc( size_t _Count,size_t _Size); //有几个数据:count,每个数据占用的字节数:size
//No.4 realloc  内存重新申请,保留原数据
void*  realloc(void*  _Block,size_t _Size); //原指针,重新申请的内存大小
//No.5 释放内存函数
void  free(void* _Block);
//__cdecl 调用准则,限定参数传参顺序(入栈顺序)--->了解一下
//_In_ 参数类型修饰 传入
//_out_ 传出参数
//size_t  unsigned int
//void* 使用前必须强制类型转换
  • 一维数组
  • 二维数组

C语言函数与指针

指针函数

指针函数就是用到指针函数,它是一个函数

  • 指针当作函数参数(如何修改实参)
  • 指针当作函数返回值
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void modify(int count)  //int count=1
{
    count = 111;
}
void modifyCount(int* p) //int *p=&count
{
    *p = 999;
}
//址传递
//值传递
/*
    子函数想要修改普通变量实参,需要传入普通变量地址:一级指针,在修改的是普通变量
    子函数想要修改指针变量实参,需要传入指针变量地址: 二级指针,在修改的是一级指针
*/
int g_num = 1992;
void modifyPoint(int** p) 
{
    //修改了一级指针变量值
    *p = &g_num;
}
//---->无头链表
//当做函数返回值--->不能返回局部变量地址
//warning C4172: 返回局部变量或临时变量的地址: num
int* returnPoint() 
{
    int num = 100;        //局部变量, 100会被回收
    return &num;
}

int* returnPoint2(int* p)
{
    return p;
}
//也是被允许,堆区内存不会自动回收
int* returnMalloc() 
{
    int* p = (int*)malloc(sizeof(int));
    assert(p);
    *p = 11334;
    return p;
}
int main() 
{
    int count = 1;
    modify(count);
    printf("%d\n", count);
    modifyCount(&count);
    printf("%d\n", count);    //999
    int* p = &count;
    printf("%d\n", *p);
    modifyPoint(&p);
    printf("%d\n", *p);
    int* result = returnPoint();
    printf("%d\n", *result);
    printf("%d\n", *result);
    printf("%d\n", *result);
    result = returnPoint2(p);
    int* pMory = (int*)malloc(sizeof(int) * 3);
    result = returnMalloc();
    free(result);
    result = NULL;
    free(pMory);
    pMory = NULL;
    return 0;
}

函数指针

函数指针是什么

函数指针: 指向函数的指针,(函数名就是函数指针,&函数指针)

作用: 用来指向函数,调用函数

如何创建一个函数指针变量

(*指针名)替换函数名 剩下照抄,形参名可以省略,这就是函数指针

通过和函数指针调用函数

  • 直接用指针名替换函数去调用函数
  • (*指针名)替换函数名调用函数

函数指针一般是用在充当函数的参数

(专业术语叫做回调函数)

typedef与函数指针

  • typedef基本用法
  • typedef与函数指针
#include <stdio.h>


void print(int(*p)(int, int), int a, int b) 
{
    printf("%d\n", p(a, b));
}
//和上面函数一样的
typedef int(*FUNC)(int, int);
void print2(FUNC p, int a, int b)   //FUNC p     int(*p)(int, int);
{
    printf("%d\n", p(a, b));
}
//typedef 给结构体起别名 后面讲
int main()
{
    //No.1 给基本数据类型起别名
    typedef int 整数;
    int a1 = 1;
    整数 a2 = 1;            //int  a2 = 1
    //typedef unsigned int  size_t;
    size_t a3 = 1;
    //No.2 数组起别名
    typedef int ARRAY[3];
    ARRAY arr = { 1,2,3 };                    // int arr[3];   int arr[3];
    for (int i = 0; i < 3; i++) 
    {
        printf("%d\n", arr[i]);
    }
    ARRAY array[3] = { 1,2,3,4,5,6,7,8,9 };    // int array[3][3];
    typedef int A2D[3][3];
    A2D array2d;                            // int array2d[3][3];
    return 0;
}

万能指针充当函数指针

  • 万能指针: 使用必须做强制类型转换
  • 指针类型: 去掉变量名

C语言自定义类型

结构体

  • 什么是结构体 (不同类型的变量集合,数组: 相同类型变量集合)

  • 创建结构体的语法
struct 结构名
{
    //结构体成员: 变量
};
//结构体一般描述是一系列事物的抽象
//抽象的是共同的属性(特征-->数据描述)
  • 如何结构体:充当记录复杂数据功能

    • 定义一个结构体变量

      • 普通结构体变量: 类型 变量名;
      • 指针变量: 类型* 变量名;
      • 数组: 类型 数组名[数组长度];
    • 如何访问结构体中数据

      • 结构体中数据只能通过结构体变量(当然指针算是特殊结构体变量,数组特殊结构体变量)
      • 访问方式只有两种

        • 变量.成员
        • 指针->成员 ->:指针指向运算符
  • 结构体变量的初始化

    • 定义的初始化,必须类型上要和成员顺序一致
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <malloc.h>
struct student 
{
    char name[20];
    int age;
    int math;
    int score;
};

int main() 
{



    //No.1 定义变量
    struct student  memory = { "Iloveyou",18,59,59};
    struct student joker;
    //先创建后初始化,成员变量以前是怎么用,现在就怎么用
    //前缀的含义是主体,表示整个成员是谁。
    //joker.name = "joker";  //字符数组不能直接复制
    strcpy_s(joker.name, 20, "joker");
    joker.age = 18;
    joker.math = 99;
    joker.score = 99;
    //No.2 用户输入结构体变量
    struct student sun;
    printf("请输入学生信息:");
    scanf_s("%s%d%d%d", sun.name, 20, &sun.age, &sun.math, &sun.score);
    printf("%s\t%d\t%d\t%d\n", sun.name, sun.age, sun.math, sun.score);
    //No.3 结构体数组--->管理表格数据
    printf("请输入3个学生信息:");
    struct student array[3];
    for (int i = 0; i < 3; i++) 
    {
        scanf_s("%s%d%d%d", array[i].name, 20, &array[i].age, &array[i].math, &array[i].score);
    }
    printf("姓名\t年龄\tmath\tscore\n");
    for (int i = 0; i < 3; i++) 
    {
        printf("%s\t%d\t%d\t%d\n", array[i].name, array[i].age, array[i].math, array[i].score);
    }
    printf("姓名\t年龄\tmath\tscore\n");
    for (int i = 0; i < 3; i++)
    {
        printf("%s\t%d\t%d\t%d\n", (array + i)->name, (array + i)->age, (array + i)->math, (array + i)->score);
    }
    //No.4 指针
    struct student* p = NULL;
    //p->age = 12;   //错误,空里面没有这个age
    //结构体指针变成结构体变量
    //4.1 指向普通变量
    p = &memory;
    printf("%s\t%d\t%d\t%d\n", p->name, p->age, p->math, p->score);
    printf("%s\t%d\t%d\t%d\n", (*p).name, (*p).age, (*p).math, (*p).score);
    //->: 怎么打出来 先打减号 在打大于号
    //4.2 动态内存申请
    struct student* pM = (struct student*)malloc(sizeof(struct student));
    assert(pM);
    scanf_s("%s%d%d%d", pM->name, 20, &pM->age, &pM->math, &(*pM).score);
    printf("%s\t%d\t%d\t%d\n", pM->name, pM->age, pM->math, pM->score);
    free(pM);
    pM = NULL;
    return 0;
}

联合体

#include <stdio.h>
#include <stdlib.h>
//联合体:共用体
union  MM
{
    char name[20];
    int age;
};
//大家基本不用,但是网络库中

int main() 
{
    //所有数据成员用的是同一段内存,最长的那个变量的内存
    //所以定义的时候不能直接初始化两个数据
    //任何时候共用体只有一个数据有效
    union MM mm = { "A" };
    //union MM mm2 = { "A" ,68};  //错误的写法
    printf("%d\n", mm.age);
    //union MM mm2 = { 68 };   //00 00 00 44    //44 00 00 00
    //printf("%s", mm2.name);

    return 0;
}

枚举类型

#include <stdio.h>
#include <stdlib.h>
//enum 枚举类型名{符号1,符号2,...};
enum COLOR{Red,Green,Blue,Yellow};
//枚举类型就是一个常量,就是整数
//默认初始化: 从0开始一次初始化
//增加代码可读性,防止幻数-->大量的常量
//手动初始化
enum  Eement {Wall=1,Road=0,Box=4,End=3,People=5};
//部分初始化,部分默认
//不写的是写了+1
enum NUM {a=1,b,c=4,d};
//b=a+1:2
//d=c+1:5
void print(int num) 
{
    printf("%d\n", num);
}
//C语言把枚举理解int
void printEnum(enum COLOR cnum) 
{
    printf("%d\n", cnum);
}
//enum class
int main() 
{

    printf("red:%d\n", Red);
    printf("Green:%d\n", Green);
    printf("Blue:%d\n", Blue);
    printf("Yellow:%d\n", Yellow);
    printf("%d\t%d\t%d\t%d\n", a, b, c, d);
    print(Red);
    printEnum(a);
    printEnum(1);
    return 0;
}

位段

#include <stdio.h>
//特殊的结构体:内存精确到二进制位
//作用:节省内存
struct Data 
{
    unsigned int a : 3;  //_ _ _   000  111:7
    unsigned int b : 1;  //0 1
    unsigned int c : 4;  //0000 1111:15
};
//寄存器的描述
//标志位
//12*4=48
//4个字节

int main()
{
    struct Data data = { 8,1,18 };
    printf("%d,%d,%d", data.a, data.b, data.c);
    return 0;
}
Last modification:April 2, 2022
如果觉得我的文章对你有用,请随意赞赏