C语言程序

一、HelloWorld原理

#include<stdio.h>
int main()
{
   printf("Hello World");
   return 0;
}

8e568e4912a646c5bb3e6830b7409b93.png

int main() 表示程序主入口,int表示整数类型 

{}表示范围

printf表示输出语句,()为输出的内容

;语句结束

return表示运行到这里就结束了

0是运行的结果,0为正常结束,非0为正常结束。int与0对应

以上为业务代码


#include表示找的内容,stdio标准处理

以上为预处理

二、HelloWorld执行流程分析

(一)编写

(二)编译

会自动生成一个.obj文件

(三)连接/链接

将二进制.obj和.h文件组合成.exe(可执行文件)

(四)运行

三、C语言核心语法

(一)注释

单行注释//注释信息

多行注释/*注释信息*/

主要作用:增加代码的阅读性

删除多个单行注释:alt+鼠标

修改字体和颜色:工具—环境—字体和颜色

注释快捷键:ctrl+K,ctrl+C

取消快捷键:ctrl+K,Ctrl+U

注释内容不参与运行,注释最好不要嵌套

(二)关键字 

被C语言赋予了特定含义的英文单词(共32个)

特点:关键字的字母全部小写;常用的代码编辑器,针对关键字有特殊的颜色标记,非常直观

(三)常量

在程序的执行过程中,其值不能发生改变的数据

1.分类

整形常量:正数、负数、0

实型常量:所有带小数点的数字 小数点前后为0,可省略;科学计数法是实型常量,但是要写E

字符常量:单引号引起来的字母、数字、英文字母

字符串常量:双引号引起来

2.输出常量

printf(参数1,参数2);         输出语句

必填:输出内容的最终样式,以字符串的形式体现

选填:填补的内容

整型

%d
实型%f
字符%c
字符串%s
#include<stdio.h>
int main()
{
    //输出常量
    //输出整数
    printf("%d",18);
    //2.输出小数
    printf("%f",1.93);
    //3.输出字符
    printf("%c",'A');
    //4.输出字符串
    //第一种方式:
    printf("Hello World");
    //第二种方式:
    printf("今年是:%s","2024年");
    return 0;
}

37d3f82001904ee0bbf8cb9006c4da71.png

输出多个常量时用隔开

输出换行(windows系统)用换行符/r/n

printf("性别:%s\n","男");

3.变量

存储数据的盒子,不是里面的数据

(1)定义格式

          int  a;

数据类型  变量名;

(2)使用

1.赋值、修改值

a=10;

a=20;

2.使用变量中记录的值

int a=10;

int b=20;

int c=a+b;           参与变量

printf("%d\n",c);  输出打印

#include<stdio.h>
int main()
{
    int a;
    a=10;
    printf("%d\n",a);
    int b=20;
    printf("%d\n",b);
    //求和
    printf("%d\n",a+b);
    //或者定义一个新变量
    int c=a+b;
    printf("%d\n",c);
}

c1fddc5d4c0a46ca89f23fa5091f8cf8.png

 vs:变量定义的位置可以是任意的

vc:变量需要统一定义在上面,然后才能开始使用

(3)注意事项

1.只能存一个值

2.变量名不允许重复定义

3.一条语句可以定义多个变量

4.变量在使用之前一定要赋值

5.变量的作用域范围

(四)计算机的存储规则

1.十进制

逢十进一,借一当十

2.二进制

由0和1构成,逢二进一,借一当二

代码中以0b开头

在计算器中,任何数据都是以二进制的形式来存储的

  • 数字转成二进制
  • 字母、汉字在存储过程中会转换成码表中对应的二进制数字
  • 图片存储--分辨率、像素            图片存储--三原色:红绿蓝(0~255)
  • 声音数据:对声音的波形图进行采样再存储

3.八进制

二进制中的数字每三位分成一组,由0~7组成

代码在以0开头

4.十六进制

二进制中的数字每四位分成一组,由0~9和a~f组成

代码中以0x开头

5.任意进制转十进制

公式:系数*基数的权次幂结果再相加

系数:每一位上的数

基数:当前进制数

权:从右向左,依次为0 1 2 3...

二进制转十进制:8421快速转换法

6.十进制转其他进制

除基取余法:不断的除以基数(几进制,基数就是几)得到余数,直至商为0,再将余数倒着拼起来即可。

(五)数据类型

1.作用

字节:最小的存储单元

  • 变量中能存储什么类型的数据
  • 存储空间的大小

2.类型

整数

short2
int4
long4/8
long long8

 不同的数据类型所表示的范围和内存大小都不一样,由编译器来决定,可以用sizeof来确定

利用sizeof测量每一种数据类型占用多少字节:sizeof(变量名/数据类型)%zu

signed有符号整数:正数、负数;unsigned(%u)无符号整数:正数

小数

float(%f)4
double(小数默认)8

 float单精度小数,精确度小数点后6位;double双精度小数,精确度小数点后15位

unsigned和小数double、float不能一起 

字符

字符取值范围内存
charASCII码表中的字母、数字、英文符号1

#include<stdio.h>
int main()
{
  //1.定义char数据类型的变量
  //char字符 取值范围ASCII码表中所有内容windows1个字节
  char c1='a';
  printf("%c\n",c1);
  char c2='1';
  printf("%c\n",c2);
  char c3='A';
  printf("%c\n",c3);
  char c4='.';
  printf("%c\n",c4);

  //2.利用sizeof测量占用多少字节
  printf("%zu\n",sizeof(char));
  printf("%zu\n",sizeof(c1));
  return 0;
}

 4703e6f6d21b4824af4ad283e20235ac.png

 (六)标识符

代码中所有我们自己起的名字,比如变量名、函数名

1.命名规则--硬性要求

  • 由数字、字母、下划线(-)组成
  • 不能以数字开头
  • 不能是关键字
  • 区分大小写

软性建议 

  • 用英文单词,见名知意
  • 变量名:全部小写
  • 代码文件名:全部小写,单词之间用下划线隔开,开头可以用数字

(七)键盘录入scanf

作用:获取用户在键盘上输入的数据,并赋值给变量

#include<stdio.h>
int main()
{
  //1.定义一个变量来接收数据
  int a;
  //2.键盘录入
  scanf_s("%d",&a);
  //3.打印
  printf("%d\n",a);
  return 0;
}

b3c8c14863414f77984292779e2a9d86.png

 字符串变量的定义方式:

数据类型 变量名[大小]=字符串;

char str[内存占用大小]=“aaa”;

内存占用大小的计算方式:

英文:1个字母、符号、数字占用一个字节

中文:一个中文占用两个字节

结束标记:1个字节

使用方式:scanf("占位符",&变量名);

scanf("%d",&a);

注意事项

  • 占位符和后面的变量要一一对应
  • 第一个参数中不写换行
  • 直接使用会有安全问题 
#include<stdio.h>
int main()
{
  int num1;
  int num2;
  printf("请输入两个整数:");
  scanf("%d %d",&num1,&num2);
  printf("%d\n",num1+num2);
}

 a049aa067dc54d969a5b78b4079391a0.png

(八)运算符 

+
-
*
/
%取余
#include<stdio.h>
int main()
{
  //除法
  printf("%d\n",10/2);
  printf("%lf\n",10/3.0);
  //取余
  printf("%d\n",10%3);
  printf("%d\n",-10%3);
  printf("%d\n",10%-3);
  return 0;
}

7ddb3275d3b343378d227ea9f435593b.png

1.整数计算,结果一定是整数

2.小数计算,结果一定是小数

3.整数和小数计算,结果一定是小数

4.小数直接参与计算,结果可能不准确

5.不能除以0

6.取余运算数据不能有小数,必须全部为整数

7.获取余数的正负与第一个数字保持一致

数值拆分 

键盘录入一个三位数,将其拆分为个位、十位、百位后,打印在控制台

#include<stdio.h>
{
  printf("请输入一个三位数:\n");
  int num;
  scanf("%d",&num);
  printf("%d\n",num);
  int num1=num%10;
  int num2=num/10%10;
  int num3=num/100;
  printf("个位是:%d\n",num1);
  printf("十位是:%d\n",num2);
  printf("百位是:%d\n",num3);
  return 0;
}

91445f4d96ff41de96b4d4f64ef115bd.png

隐形转换

计算时,数据类型不一样不能直接运算,需要转成一样的,才能运算

把一个取值范围小的,转成取值范围大的

double>float>long long>long>int>short>char

short、int类型的数据在运算的时候,先提升为int,再进行运算 

强制转换

把取值范围大的,赋值给取值范围小的

格式:目标数据类型 变量名=(目标数据类型)被强转的数据

有可能导致错误

字符相加

字符跟数字进行转换的时候查询ASCII码表

自减自加运算符

++变量中的值+1
--变量中的值-1

++和--既可以放在变量前面,也可以放在变量的后面

++和--放在变量的前边或后边,单独写一行结果是一样的

后++先用后加;前++先加后用

windows系统中前缀优先于后缀,前缀统一自增、自减,再把结果拿出来用;后缀统一先用,等整个表达式中变量用完了,再进行自增

mac、Linux:前缀和后缀的优先级一样,从左向右一次计算,每一个前缀和后缀都是一个独立的个体

=直接赋值int a=10,将10赋值给a
+=加后赋值a+=b,将a+b的值给a
-=减后赋值a-=b,将a-b的值赋值给a
*=乘后赋值a*=b,将a*b的值给a
/=除后赋值a/=b,将a/b的商给a
%=取余赋值a%=b,将a%b的余数给a

#include<stdio.h>
int main()
{
  int a=10;
  int b=20;
  a+=b;//a=a+b
  printf("%d\n",a);
  printf("%d\n",b);
  a*=b;//a=a*b
  printf("%d\n",a);
  printf("%d\n",b);
  return 0;
}
  

1b7d07f88cc14a0a96ffba61bc4e83a0.png

关系运算符 

==a==b判断a是否等于b
!=a!=b判断a是否不等于b
>a>b判断a是否大于b
>=a>=b判断a是否大于等于b
<a<b判断a是否小于b
<=a<=b判断a是否小于等于b

成立1(真),不成立0(假)

#include<stdio.h>
int main()
{
  int a=10;
  int b=20;
  printf("%d\n",a==b);
  printf("%d\n",a!=b);
  printf("%d\n",a>b);
  printf("%d\n",a>=b);
  printf("%d\n",a<b);
  printf("%d\n",a<=b);
  return 0;
}

 edd240ee5893433ebfb970bcb99209b2.png

#include<stdio.h>
int main()
{
  //1.判断一个数是否为偶数
  int a=11;
  printf("%d\n",a%2==0);
  //2.判断一个数是否不超过100
  int number=100;
  printf("%d\n",number<=100);
  //3.判断银行卡余额是否足够
  int pay=100;
  int money=200;
  printf("%d\n",pay<=100);
  //4.判断货物库存是否足够
  int buy=10;
  int ware=100;
  printf("%d\n",ware>=buy);
  //5.商品不低于100元
  int goods=500;
  printf("%d\n",goods>=100);
  return 0;
}

d65914da366f45348b539ff84ba01c29.png

逻辑运算符 

&&与(而且)两边都为真,结果才是真
||或(或者)两边都为假,结果才是假
非(取反)取反

&& ||具有短路效果 

#include<stdio.h>
int main()
{
  printf("%d\n",1&&1);
  printf("%d\n",0&&0);
  printf("%d\n",1&&0);
  printf("%d\n",0&&1);
  printf("--------------\n");
  printf("%d\n",1||1);
  printf("%d\n",0||0);
  printf("%d\n",1||0);
  printf("%d\n",0||1);
  printf("--------------\n");
  printf("%d\n",!1);
  printf("%d\n",!0);
  return 0;
}

dba018ea6d9b4c0d88f0b3ebdd29cfc9.png

键盘录入一个两位数,要求该数字不能包含7,也不能是7的倍数,符合要求输出1,不符合要求输出0

#include<stdio.h>
int main()
{
  int number;
  printf("请输入一个两位数:\n");
  scanf("%d",&number);
  printf("%d\n",number);
  int num1=number%10;
  int num2=number/10%10;
  printf("%d\n",num1!=7&&num2!=7&&number%7!=0);
  return 0;
}

 1d5b38e6fd42419ba5b1ac69125ae4ac.png

三元运算符 

格式:关系表达式?表达式1:表达式2

#include<stdio.h>
int main()
{
  //获取三个变量中的较大值
  int a=10;
  int b=20;
  int c=30;
  //先求出两个数据的较大值
  int temp=a>b?a:b;
  int max=temp>c?temp:c;
  printf("%d\n",max);
  return 0;
}

e92ab82bb8b140d2b813256c4a90f44f.png

逗号运算符 (分隔符)

1.从左往右依次运算

2.最后一个子表达式的结果,是整个表达式的结果

3.优先级最低

#include<stdio.h>
int main()
{
  int i;
  printf("%d\n",(i=3,++i,i++,i+5));
  return 0;
}

2641581c968343f6878b786a7c5e7716.png

#include<stdio.h>
int main()
{
  //1.定义变量
  int number=17;
  //2.变成该数字的绝对值
  number>=0?number:-number;
  //3.除以三获取余数
  number %=3;
  //4.乘以10;
  number*=10;
  printf("%d\n",number);
  return 0;
}

2f71068dd1e542cfbdf7ab6e3ebde6a1.png

运算符优先级 

小括号优先于所有

一元>二元>三元

&&>||>赋值

#include<stdio.h>
int main()
{
  int w=4,x=3,y=2,z=1;
  int number=w<x?w:z<y?z:x;
  printf("%d\n",number);
  return 0;
}

aa74f93b3d364208bcbc0f9d222e16cc.png

四、流程控制语句

(1)顺序结构

从上往下依次执行,是程序默认的执行过程,是程序默认的执行流程

(2)分支结构

程序在执行过程中出现了岔路

1.if语句

作用:如果满足条件,才可以执行指定的代码

使用场景:某些代码,在满足条件的时候才执行

格式

#include<stdio.h>
int main()
{
  //判断变量a是否大于10,如果成立,就打印语句
  int a=20;
  if(a>10)
  {
    printf("真的大于10");
  }
  return 0;
}

e18d91073f624da98a549dcbe930c279.png

#include<stdio.h>
int main()
{
  //初始最大生命200
  //收到了伤害:80
  //恢复了:100
  //当前血量多少
  //定义变量记录数据
  int blood=200;
  int damage=80;
  int restore=100;
  blood=blood-damage+restore;
  printf("%d\n",blood);
  //判断
  if(blood>200)
  {
    blood=200;
  }
  printf("%d\n",blood);
}

 f9b8c7c541174c9e969e9a490325d918.png

在C语言中,如果判断的结果是一个数字,非0表示条件成立,0表达条件不成立

如果大括号里面的语句体只有一行,那么大括号可以省略不写 

第二种格式

#include<stdio.h>
int main()
{
  int satisfy=1;
  if(satisfy)
  {
    printf("终身大事由父母做主");
  }
  else
  {
    printf("女儿还想孝敬父母两年");
  }
}

c571e61257f14dc99969ed8580a16087.png

多条件判断

 从上往下依次进行判断,只要有一个判断成立,就执行对应的语句体,如果所有的判断都不成立,就执行else的语句体

#include<stdio.h>
int main()
{
  //1~99:VIP1;100~499:VIP2;500~999:VIP3;1000~1999:VIP4;2000以上:VIP5
  //1.键盘录入总金额
  int money;
  printf("请输入总额度\n");
  scanf("%d",&money);
  //2.判断总额度
  if(money==0)
  {
    printf("0冲玩家");
  }
  if(money>=1&&money<=99)
  {
    printf("VIP1\n");
  }
  else if(money>=100&&money<=499)
  {
    printf("VIP2\n");
  }
  else if(money>=500&&money<=999)
  {
    printf("VIP3\n");
  }
  else if(money>=1000&&money<=1999)
  {
    printf("VIP4\n");
  }
  else
  {
    printf("VIP5\n");
  }
  return 0;
  
}

6f7c3796accd4e6e8d495971a8b908cb.png

2.switch语句

作用:把所有的选择一一列举出来,根据不同的条件任选其一 

执行流程:首先计算表达式的值,依次和case后面的值进行比较,如果有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束,如果所有的case后面的值和表达式的值都不匹配,就会执行default里面的语句体,然后结束整个switch语句

#include<stdio.h>
int main()
{
  int week=1;
  switch(week)
  {
    case 1:
        printf("1");
        break;
     case 2:
        printf("2");
        break;
     case 3:
        printf("3");
        break;
     case 4:
        printf("4");
        break;
     case 5:
        printf("5");
        break;
     case 6:
        printf("6");
        break;
     case 7:
        printf("7");
        break;
    default:
      printf("没有这个星期");
      break;
  }
}

db56a262281840bf92d22df35f875c16.png

表达式:计算结果只能是(字符/整数)

case:值只能是(字符/整数)字面量,不能是变量

case:值不允许重复

break:表示中断,结束的意思,结束switch语句

default:所以情况都不匹配,执行该处的内容

switch:有限个case进行匹配的情况,10个左右

if:一般是对一个范围进行判断 

case穿透的规则:1.还是根据小括号表达式的结果去匹配对应的case

2.执行对应case里面的代码

3.如果在执行过程中,遇到了break,那么直接结束整个switch

但是如果没有遇到break,就会继续执行下面case中的代码,直到遇到break,或者把整个switch中所有的代码全部执行完了,才会结束

4.case只会往下穿透,不会回到上面

#include<stdio.h>
int main()
{
  /*键盘录入月份数,输出对应的季节
    春季:3/4/5
    夏季:6/7/8
    秋季:9/10/11
    冬季:12-次年2月
  */
  //1.键盘录入月份数
  int month;
  printf("请输入对应月份:\n");
  scanf("%d",&month);
  printf("%d\n",month);
  //2.根据月份输出对应的季节
  switch(month)
  {
  case 1:
      printf("冬季\n");
      break;
  case 2:
      printf("冬季\n");
      break;
  case 3:
      printf("春季\n");
      break;
  case 4:
      printf("春季\n");
      break;
  case 5:
      printf("春季\n");
      break;
  case 6:
      printf("夏季\n");
      break;
  case 7:
      printf("夏季\n");
      break;
case 8:
      printf("夏季\n");
      break;
  case 9:
      printf("秋季\n");
      break;
  case 10:
      printf("秋季\n");
      break;
  case 11:
      printf("秋季\n");
      break;
  case 12:
      printf("冬季\n");
      break;
  default:
      printf("没有这个月份\n");
      break;
}

33210816075440e7a9accbf7d43e6e61.png

 循环条件

开始条件(初始化语句)、结束条件(条件判断语句)、控制开关(条件控制语句)、循环体语句(重复执行的代码)

#include<stdio.h>
int main()
{
  //打印5遍啊啊啊
  for(int i=0;i<=5;i++)
  printf("啊啊啊\n");
  return 0;
}

5bec8ad6893641e5a446a923ad67d0b9.png

执行流程: 1.执行初始化语句

2.执行条件判断语句,看其结果是否成立

成立:执行循环体语句;不成立:结束循环

3.执行条件控制语句

4.回到2继续执行条件判断语句

vc中所以变量要先统一定义在最上方

#include<stdio.h>
int main()
{
  //定义一个变量用于累加
  int sum=0;
  //打印1-5
  for(int i=1;i<=5;i++)
  {
    sum=i+sum;
  }
  //打印sum
  printf("%d",sum);
  return 0;
}

ba6e7202267d43ae85fe9772514aa45a.png

变量的生命周期:变量只在所属的大括号中有效

#include<stdio.h>
int main()
{
  //1-100间的偶数和
  //定义一个变量用于累加
  int sum=0;
  //获取1-100间的数字
  for(int i=1;i<=100;i++)
  {
    if(i%2==0)
    {
      sum=sum+i;
    }
  }
  //打印
  printf("%d\n",sum);
  return 0;
}

544924dae49f4611b2fb839ce025c381.png

#include<stdio.h>
int main()
{
    //1.键盘录入两个数字
    int num1;
    int num2;
    printf("请输入两个整数:\n");
    scanf("%d %d", &num1, &num2);
    //判断大小
    int min = num1 < num2 ? num1 : num2;
    int max = num1 > num2 ? num1 : num2;
    //2.定义一个变量用来统计个数
    int count = 0;
    //获取范围中的每一个数字
    for (int i = min;i <= max;i++)
    {
        if (i % 6 == 0 && i % 8 == 0)
        {
            count++;
        }
    }
   //4.打印
   printf("在这个范围中,既能被6整除又能被8整除的数字一共有%d个\n", count);
   return 0;
}

8d7bedb855c847d3aac98397bac12430.png

2.while循环 

#include<stdio.h>
int main()
{
  int i=1;
  while(i<=3)
  {
    printf("123\n");
    i++;
  }
  return 0;
}

0ea771aa20f943ffb22c5714ed4a80e3.png

for和while的对比

相同点:运行规则相同

区别:for循环中,控制循环的变量,因为归属for循环的语法结构,在for循环结束后,就不能再次被访问到了;while循环中,控制循环的变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变量还可以继续使用

for循环:知道循环次数或循环范围

while循环:不知道循环次数和范围,只知道循环的结束条件

案例1 ——2的幂次方 

#include<stdio.h>
int main()
{
  //判断整数是否为2的幂次方
  //任意的一个数字,不断除以2,最终会得到数字1,输出yes
  //结果无法被2再次整除,输出no
  int n=128;
  while(n>1&&n%2==0)
  {
    n=n/2;
  }
  if(n==1)
  {
    printf("yes\n");
  }
  else
  {
    printf("no\n");
  }
  return 0;
}

 2f3f2f1da44c4dc3848abc5fbbdc8b63.png

案例2——纸张折叠 

#include<stdio.h>
int main()
{
  /*珠穆朗玛峰8844.43米=884430毫米,一张纸厚度0.1毫米
    折叠多少次可以达到珠穆朗玛峰的高度
  */
  int height=8844430;
  double paper=0.1;
  int count=0;
  while(paper<=height)
  {
    paper=paper*2;
    count++;
  }
  printf("%d\n",count);
  return 0;
}

c209d1db5c8d4327aeb1da2e2b033bf5.png

案例3——整数反转

#include<stdio.h>
int main()
{
  //将一个整数x,进行翻转
  //从右边依次获取每个数字再次拼接起来
  int number=123;
  int rev=0;
  while(number!=0)
  {
    //获取number右边的第一个数字
    int temp=number%10;
    //去掉刚刚获取的数字
    number=number/10;
    //把刚刚获取的数字拼接到rev变量
    rev=rev*10+temp;
  }
  printf("%d\n",rev);
}
  

 b07efc81b02d47f5aa0f5706ce65e282.png

案例4——平方根

#include<stdio.h>
int main()
{
  //给你一个非负整数x,计算返回x的算术平方根,结果只保留整数部分,小数部分舍去
  //规则:从1开始找,直到第一个相乘的结果大于原来的数字,那么前一个就是他平方根的整数部分
  int number=17;
  int i=1;
  while(i*i<=number)
  {
      i++;
  }
  printf("%d\n",i-1);
  return 0;
}

53d2d511a2e84dfba2942051eed2d360.png

3.do...while语句

#include<stdio.h>
int main()
{
  int i=1;
  do
  {
    printf("%d\n",i);
    i++;
  }while(i<=5);
  return 0;
}

五、循环高级语句 

(1)无限循环

#include<stdio.h>
int main()
{
  for(;;)
  {
    printf("123\n");
  }
  return 0;
}

#include<stdio.h>
int main()
{
  while(1)
  {
    printf("123\n");
  }
return 0;
}

 

注意点:无限循环因为永远停不下来,所以下面不能写其他代码

(2)跳转控制语句 

1.break

不能单独书写,只能写在switch,或者循环中,表示结束,跳出的意思

 2.continue                              

结束本次循环,继续下次循环

include<stdio.h>
int main()
{
  for(int i=1;i<=5;i++)
  {
    if(i==3)
    {
      continue;
    }
    printf("在吃第%d个包子\n",i);
  }
  return 0;
}

练习1-打印矩形  

#include<stdio.h>
int main()
{
  for(int i=1;i<=3;i++)
  {
    for(int j=1;j<=5;j++)
    {
    printf("*");
    }
    printf("\n");
  }
  return 0;
}

内循环是做了什么事情,外循环是把里面的代码重复了N次 

练习2-打印形状 

#include<stdio.h>
int main()
{
  for(int i=1;i<=5;i++)
  {
    for(int j=i;j<=5;j++)//决定一行打印多少*
    {
      printf("*");
    }
    printf("\n");
  }
return 0;
}

 

#include<stdio.h>
int main()
{
  for(int i=1;i<=5;i++)
  {
    for(int j=1;j<=i;j++)//决定一行打印多少*
    {
      printf("*");
    }
    printf("\n");
  }
return 0;
}

练习3-九九乘法表 

#include<stdio.h>
int main()
{
  for(int i=1;i<=9;i++)
  {
    for(int j=1;j<=i;j++)
    {
      printf("%d * %d= %d\t",j,i,i*j);///t:制表符
    }
    printf("\n");
  }
return 0;
}

\t 根据前面字母的个数在后面补空格,让整体的长度达到8或8的倍数;中文长度为2

作用:打印表格数据的时候,可对齐

练习4-统计质数

#include<stdio.h>
int main()
{
  //获取所有小于等于n的质数的数量
  int number=100;
  int count=0;
  //统计从2开始,到number-1,在这个范围之内,有多少数字能被number整除
  for(int i=2;i<number;i++)
  {
    if(number%i==0)
    {
       count++;
       break;
     }
  }  
  if(count==0)
  {
    printf("%d是一个质数\n",number);
  }
  else
  {
    printf("%d不是一个质数\n",number);
  }
  return 0;
}

#include<stdio.h>
int main()
{
  //获取所有小于等于i的质数的数量
  int countA=0;
  //统计从2开始,到number-1,在这个范围之内,有多少数字能被number整除
  for(int i=2;i<=100;i++)
  {
    int count=0;
    for(int j=2;j<i;j++)
    {
      if(i%j==0)
      {
         count++;
         break;
       }
    }  
    if(count==0)
    {
      countA++;
    }
  }
    printf("%d\n",countA);
  return 0;
}

练习5-计算数字

#include<stdio.h>
int main()
{
  //1*1+2*2+3*3...+10*10
  long long res=0;
  //外循环:依次循环1-10
  for(int i=1;i<=10;i++)
  {
    //内循环:表示外循环的数字一共要乘几次
    long long pow=1;
    for(int j=1;j<=i;j++)
    {
      pow=pow*i;
    }
    //当内循环结束之后,就表示每一个单独的选项,已经有结果了,累加
    res=res+pow;
  }
  printf("%lld",res);
return 0;
}

练习6-特殊数字

#include<stdio.h>
int main()
{
    int i, x, y, z, n = 0;
    for (i = 0;i < 1000;i++)
    {
        x = i % 10;
        y = (i / 10) % 10;
        z = (i / 100);
        if (x + y + z == 15)
        {
            n++;
            printf("%d\n", i);
        }
    }
    printf("和为15一共有%d个\n", n);
    return 0;
}

循环嵌套跳出的问题

#include<stdio.h>
int main()
{
    for (int i = 1;i <= 3;i++)
    {
        for (int j = 1;j <= 5;j++)
        {
            printf("内循环执行%d\n", j);
            //break:只能跳出内循环
            goto a;
        }
        printf("内循环结束\n");
        printf("----------\n");
    }
    //标号
    a:printf("外循环结束\n");
    return 0;
}

 

goto:结合标号,可以调到代码的任何的地方,一般只用于跳出循环嵌套 

六、函数 

函数就是程序中独立的功能

反复书写的代码,又不确定什么时候会用到的代码打包起来

#include<stdio.h>
void playGame()
{
  printf("123");
  printf("345");
}
int main()
{
  playGame();
  printf("------");
  playGame();
  return 0;
}
  

(1)函数练习-两数之和

#include<stdio.h>
void sum()
{
  int num1=10;
  int num2=20;
  int sum=num1+num2;
  printf("%d\n",sum);
}
int main()
{
  sum();
  return 0;
}
  

(2)带有参数的函数

#include<stdio.h>
void sum(int num1, int num2)
{
	int sum = num1 + num2;
	printf("%d\n",sum);
}
int main()
{
	sum(10, 20);
	sum(30, 40);
	return 0;
}

注意点:形参和实参一一对应

(3)带有返回值的函数

#include<stdio.h>
//计算分数a93+10和b87+9,比较大小
int sum(int base, int additional)
{
	int sum = base + additional;
	return sum;
}
int main()
{
	int score1 = sum(93, 10);
	int score2 = sum(87, 9);
	if (score1 > score2)
	{
		printf("a的成绩更高");
	}
	else if (score1 < score2)
	{
		printf("b的成绩更高");
	}
	else
	{
		printf("两个人成绩一样");
	}
	return 0;
}

优点:提高了代码的复用性、可维护性

(4)格式 

返回值类型 函数名(形参1,形参2...)

{

  函数体;

  return 返回值;

}

变量= 函数名(实参...);

printf("占位符",函数名(实参...));

定义函数的终极绝招(三个问题) 

1.我定义函数是为了什么?函数体

2.我干这件事情,需要什么才能完成?形参

3.我干完了这件事情,调用处是否需要继续使用?返回值类型

需要继续使用 必须写;不需要返回 void

#include<stdio.h>
//给定两个长方形,判断谁的面积更大?如何定义函数
//1.定义函数求长方形面积
//2.需要长/宽
//3.需要继续使用
double getArea(double len, double width)
{
    double area = len * width;
    return area;
}
int main()
{
    //调用函数求长方形面积
    double area1 = getArea(5.3, 2.7);
    double area2 = getArea(1.5, 0.8);
    if (area1 > area2)
    {
        printf("第一个长方形面积大");
    }
    else if (area1 < area2)
    {
        printf("第二个长方形面积大");
    }
    else
    {
        printf("两个长方形面积一样大");
    }
    return 0;
}

 (5)函数的注意事项

1.函数不调用就不执行

2.函数名不能重复

3.函数与函数之间是平级关系,不能嵌套定义

4.自定义函数写在main函数的下面,需要在什么申明

5.return下面,不能编写代码,永远执行不到,属于无效的代码

6.函数的返回值类型为void,表示没有返回值,return可以省略不写

如果书写了return,后面不能跟具体数值,仅表示结束函数

(6)常见的函数

<math.h>数学的常见的函数

pow() 幂

sqrt() 平方根

ceil()向上取整

floor()向下取整

abs()绝对值

<time.h>

time()获取当前时间

#include<stdio.h>
#include<math.h>
int main()
{
/*
math.h
pow() 幂
sqrt() 平方根
ceil()向上取整
floor()向下取整
abs()绝对值
<time.h>
time()获取当前时间*/
    //1.pow幂
    double res1 = pow(2, 3);
    printf("%lf\n", res1);
    //2.sqrt平方根
    double res2 = sqrt(8);
    printf("%lf\n", res2);
    //3.向上取整(进一法)
    double res3 = ceil(12.3);
    printf("%lf\n", res3);
    //4.floor向下取整(去尾法)
    double res4 = floor(12.7);
    printf("%lf\n", res4);
    //abs绝对值
    int res5 = abs(-13);
    printf("%d\n", res5);
    return 0;
}

#include<stdio.h>
#include<time.h>
int main()
{/*
<time.h>
time()获取当前时间*/
    //形参:表示获取的当前时间是否需要在其他时间进行存储
    //一般来讲,不需要在其他地方进行存储,NULL(大写)
    //返回值:long long
    //结果是从1970年1月1日0:0:0到现在的秒数
    long long res=time(NULL);
    printf("%lld\n", res);
    return 0;
}

(7)随机数

伪随机数---线性同余方程

C语言中获取随机数1.使用srand设置种子  2.使用rand获取随机数

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //1.设置种子
    //初始值,因为每一个随机数都是通过前一个数字再结合一系列复杂的计算得到的
    srand(1);
    //2.获取随机数
    for (int i = 1;i <= 10;i++)
    {
        int num = rand();
        //3.输出打印
        printf("%d\n", num);
    }
    return 0;
}

弊端 

1.种子不变,随机数结果是固定的

2.随机数的范围不能自己指定(0~32767)

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
    //1.设置种子
    //初始值,因为每一个随机数都是通过前一个数字再结合一系列复杂的计算得到的
    //用一个变化的数据去充当种子 时间
    /*任意范围内获取一个随机数
    绝招:用于生产任意范围内的随机数(7~23)
    1.把这个范围变成包头不包尾,包左不包右(7~24)
    2.拿着尾巴-开头24-7=17
    3.修改代码*/
    srand(time(NULL));
    //2.获取随机数
    for (int i = 1;i <= 10;i++)
    {
        int num = rand()%17+7;
        //3.输出打印
        printf("%d\n", num);
    }
    return 0;
}

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
    //1.生成1-100之间的随机数
    srand(time(NULL));
    int number = rand()%100+1;
    //2.利用循环+键盘录入去猜
    //随机数:50
    //键盘录入:40 小了;60 大了;50中了 程序停止
    //猜的数字
    int guess;
    while (1)
    {
        printf("请输入您要猜的数字: \n");
        scanf("%d",&guess);
        if (guess<number)
        {
            printf("小了\n");
        }
        else if (guess >number)
        {
            printf("大了\n");
        }
        else
        {
            printf("中了\n");
            break;
        }
    }
    return 0;
}

七、数组 

(1)定义

数组:是一种容器,可以用来存储同种数据类型的多个值

数据类型 数组名[长度]

int arr [3]

特点1:连续的空间

特点2:一旦定义,长度不可变 

#include<stdio.h>
int main()
{
   /*1.定义数组存储全班80位同学的年龄
   2.定义数组存储全班50位同学的身高
   3.定义数组存储全身每件衣服的价格*/
    int arr1[80];
    double arr2[50];
    double arr3[3];
    return 0;
}

(2)初始化

定义数组的时候,第一次给数组赋值

数据类型 数组名[长度]={数据值,数据值...}

int arr [3]={1,2,3}

长度省略:数据值的个数就是数组的长度

长度未省略:数据值的个数<=长度 

如果数组的长度没有写,数据值的个数就是数组的长度

如果数组的长度已经写上,数据值的个数<=长度 

未赋值的部分有默认值

整数:0

小数:0.0

字符:'\'

字符串:NULL(什么都没有)

#include<stdio.h>
int main()
{
   /*1.定义数组存储5位同学的年龄,并初始化
   2.定义数组存储身高,并初始化*/
    int arr1[5] = { 17,18,19,20,21 };
    int arr2[] = { 17,18,19,20,21 };
    double arr3[2] = { 1.68,1.53 };
    double arr4[] = { 1.68,1.53 };
    double arr5[2] = {0.0};
    return 0;
}

(3)元素访问

索引就是数组的一个编号,也叫作:角标、下标、编号(0~数组长度-1)

特点:从0开始的,连续+1,不间断

数组名[索引]              //偏移量

int num=arr[5]

printf("占位符",数组名[索引]);

printf("占位符",arr[5]); 

数组名[索引]=数据值;

arr[5]=10; 

#include<stdio.h>
int main()
{
   /*定义一个长度为5的数组,并进行初始化:1 2 3 4 5
   1.获取索引为0,2,4的元素,并求和
   2.把最后一个索引上的元素修改为10*/
    int arr[5] = { 1,2,3,4,5 };
    int num1=arr[0];
    int num2=arr[2];
    int num3=arr[4];
    int sum = num1 + num2 + num3;
    printf("%d\n", sum);
    printf("修改前:%d\n", arr[4]);
    arr[4] = 10;
    printf("修改后:%d\n", arr[4]);
    return 0;
}

(4) 遍历

依次获取数组中的每一个元素

#include<stdio.h>
int main()
{
   /*定义一个长度为5的数组,并进行初始化:1 2 3 4 5
   遍历数组,并把每一个元素打印到控制台*/
    int arr[5] = { 1,2,3,4,5 };
    for (int i = 0;i <5;i++)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

(5)内存和内存地址

内存:软件在运行时,用来临时存储数据的

1.把数据保存到内存中 2.从内存的‘对应位置’把数据取出来

内存地址:内存中每一个小格子的编号

作用:快速管理内存空间

规则   1)32位的操作系统,内存地址以32位的二进制表示

2)64位的操作系统,内存地址以64位的二进制表示

书写的时候:转成十六进制

#include<stdio.h>
int main()
{
   //获取变量的内存地址
    int a = 10;
    printf("%p\n", &a);
    int b = 20;
    int c = 30;
    printf("%p\n", &b);
    printf("%p\n", &c);
    return 0;
}

#include<stdio.h>
int main()
{
   //获取变量的内存地址
    int a = 10;
    printf("%p\n", &a);
    int b = 20;
    int c = 30;
    printf("%p\n", &b);
    printf("%p\n", &c);
    printf("----------\n");
    //获取数组的内存地址
    int arr[] = { 1,2,3 };
    printf("%p\n", &arr);
    printf("%p\n", &arr[0]);
    printf("%p\n", &arr[1]);
    printf("%p\n", &arr[2]);
    return 0;
}

数组的长度=总长度/数据类型占用的字节个数

(6)常见问题 

数组作为函数的参数,实际上传递的是数组的首地址,如果要在函数中对数组进行遍历,记得一定要把数组的长度一起传递过去

定义处:arr表示的就是完整的数组

函数中的arr:只是一个变量,用来记录数组的首地址

#include<stdio.h>
void printfArr(int arr[], int len);
int main()
{
    int arr[] = { 1,2,3,4,5 };
    int len = sizeof(arr) / sizeof(int);
    printfArr(arr, len);
    return 0;
}
void printfArr(int arr[], int len)
{
    for (int i = 0;i < len;i++)
    {
        printf("%d\n", arr[i]);
    }
}

最小索引:0;最大索引:长度-1

(7)练习

求最值 

#include<stdio.h>
int main()
{
	//数组元素为{33,5,22,44,55},找出最大值打印到控制台
	int arr[] = { 33,5,22,44,55 };
	int max = arr[0];//max一定是数组中已经存在的数据,一般把0索引当做默认值
	int len = sizeof(arr) / sizeof(int);
	for (int i = 0;i < len;i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
	printf("%d\n", max);
	return 0;
}

 

数组求和 

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
	//生成10个1~100之间的随机数存入数组
	//求出所有数据的和
	int arr[10] = {0};
	int len = sizeof(arr) / sizeof(int);
	//2.生成10个1~100之间的随机数存入数组
	srand(time(NULL));
	
	for (int i = 0;i < len;i++)
	{
		int num = rand() % 100 + 1;
		arr[i] = num;
	}
	int sum = 0;
	for (int i = 0;i < len;i++)
	{
		sum=sum+arr[i];
	}
	printf("%d\n", sum);
	return 0;
}

 数组求和2

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int contains(int arr[], int len, int num);
int main()
{
	//生成10个1~100之间的随机数存入数组,数据不能重复
	//求出所有数据的和
	int arr[10] = { 0 };
	int len = sizeof(arr) / sizeof(int);
	//2.生成10个1~100之间的随机数存入数组
	srand(time(NULL));

	for (int i = 0;i < len;)
	{
		int num = rand() % 100 + 1;
		//存入前,先判断,如果不存在,再添加
		int flag = contains(arr, len, num);
		if (!flag)
		{
			arr[i] = num;
			i++;
		}
	}
	for (int i = 0;i < len;i++)
	{
		printf("%d\n", arr[i]);
	}
	//利用累加求和
	int sum = 0;
	for (int i = 0;i < len;i++)
	{
		sum = sum + arr[i];
	}
	//求平均数
	int avg = sum / len;
	//统计多少数字比平均数小
	int count = 0;
	for (int i = 0;i < len;i++)
	{
		if (arr[i] < avg)
		{
			count++;
		}
	}
	printf("平均数为%d\n", avg);
	printf("%d\n", count);

	return 0;
}
//判断num在函数中是否存在,存在,返回1;不存在,返回0
int contains(int arr[], int len, int num)
{
	for (int i = 0;i < len;i++)
	{
		if (arr[i] == num)
		{
			return 1;
		}
	}
	return 0;
}

 

反转数组 

#include<stdio.h>
void printfArr(int arr[], int len);
int main()
{
	/*录入5个数据并存入数组
	1)遍历数组
	2)反转数组
	3)再次遍历*/
	int arr[5] = { 0 };
	int len = sizeof(arr) / sizeof(int);
	for (int i = 0;i < len;i++)
	{
		printf("录入第%d个元素\n", i + 1);
		scanf_s("%d", &arr[i]);
	}
	//遍历数组
	printfArr(arr,len);
	//反转数组
	int i = 0;
	int j = len - 1;
	while (i < j)
	{
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
		i++;
		j--;
	}
	//遍历数组
	printfArr(arr, len);
	return 0;
}
void printfArr(int arr[], int len)
{
	for (int i = 0;i < len;i++)
	{
		printf("%d\n", arr[i]);
	}
}

 

打乱数组中的数据 

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
	/*定义数组,存入1~5,打乱顺序*/
	int arr[] = { 1,2,3,4,5 };
	int len = sizeof(arr) / sizeof(int);
	srand(time(NULL));
	for (int i = 0;i < len;i++)
	{
		//获取一个随机索引
		int index=rand() % len;
		//i指向的元素跟index指向的元素交换
		int temp = arr[i];
		arr[i] = arr[index];
		arr[index] = temp;
	}
	for (int i = 0;i < len;i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

(8) 常见算法

基本查找

#include<stdio.h>
int order(int arr[], int len, int num);
int main()
{
	/*顺序查找
	从数组0索引开始,依次往后查找,找到就返回数据对应索引,没有找到就返回-1*/
	int arr[] = { 11,22,55,77,44 };
	int len = sizeof(arr) / sizeof(int);
	int num = 55;
	//调用函数查找数据
	int index = order(arr,len, num);
	printf("%d\n", index);
	return 0;
}
//查找数组中的数据
//返回值:数据所在索引
int order(int arr[], int len, int num)
{
	for (int i = 0;i < len;i++)
	{
		if (arr[i] == num)
		{
			return i;
		}
	}
	return -1;
}

二分查找/折半查找

前提条件:数组中的数据必须是有序的

核心逻辑:每次排除一般的查找范围

#include<stdio.h>
int binarySearch(int arr[], int len, int num);
int main()
{
	/*二分查找,在7,23,81,103,127,131,147中查找数据*/
	int arr[] = { 7,23,81,103,127,131,147 };
	int len = sizeof(arr) / sizeof(int);
	int num = 131;
	int index = binarySearch(arr, len, num);
	printf("%d\n", index);
	return 0;
}
int binarySearch(int arr[],int len,int num)
{
	int min = 0;
	int max=len - 1;
	while (min <= max)
	{
		int mid = (min + max) / 2;
		if (arr[mid] < num)
		{
			min = min + 1;
		}
		else if (arr[mid] > num)
		{
			max = min - 1;
		}
		else 
		{
			return mid;
		}
	}
	return -1;
}

  插值查找

要求:数据要有序,且数据分布尽可能的均匀一点

优势:满足要求,效率比二分查找快,否则反而会更慢

查找算法

分块的原则1:前一块中的最大数据,小于后一块中所以的数据(块中无序,块间有序)

分块的原则2:块数数量一般等于数字的个数开根号

核心思路:先确定要查找的元素在哪一块,然后在块内挨个查找

{//块

  int max;//块中的最大值

  int startIndex;//起始索引

  int endIndex;//结束索引

}

 排序算法

冒泡排序:相邻的数据两两比较,小的放前面,大的放后面

第一轮循环结束,最大值已经找到,在数组最右边

第二轮循环只要在剩余的元素找最大值就可以了

每二轮循环结束,次大值已经确定,第三轮循环继续在剩余数据中循环

如果数据中有n个数据,总共我们只要执行n-1轮的代码就可以 

#include<stdio.h>
int main()
{
	/*冒泡排序3,5,2,1,4*/
	//定义数组存储数据
	int arr[] = {3,5,2,1,4};
	int len = sizeof(arr)/sizeof(int);
	//冒泡排序,升序排列
	for (int i = 0;i < len-1;i++)
	{
		for (int j = 0;j < len - 1-i;j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
	for (int i = 0;i < len;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

选择排序

从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较,小的放前面,大的放后面,以此类推

1、从0索引开始,跟后面的元素一一比较

2、小的放前面,大的放后面

3、第一轮循环结束后,最小的数据已经确定

4、第二轮循环从1索引开始以此类推

5、第三轮循环从2索引开始以此类推

6、第四轮循环从3索引开始以此类推

#include<stdio.h>
int main()
{
	//选择排序
	int arr[] = { 3,5,2,1,4 };
	int len = sizeof(arr) / sizeof(int);
	for (int i = 0;i < len-1;i++)
	{
		for (int j = i + 1;j < len;j++)
		{
			if (arr[i] > arr[j])
			{
				int temp=arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
			}
		}
	}
	for (int i = 0;i < len;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

八、指针 

指针=内存地址

指针变量存储内存地址的变量

定义格式

数据类型(跟指向变量的类型保持一致)*(标记)变量名(自己起的名字)

查询数据  格式 *指针名

int a=10;

int *p=&a;//*标记

printf("%d",*p);//*解引用运算符      *p//存储地址

#include<stdio.h>
int main()
{
	//利用指针去获取变量中的数据/存储数据
	int a = 10;
	//定义指针去指向变量a
	int* p = &a;
	//利用指针去获取变量中的数据
	printf("%d\n", *p);
	//利用存储数据/修改数据
	*p = 200;
	printf("%d\n", *p);
	printf("%d\n", a);
	return 0;
}

①p是指针的名字

②指针变量的数据类型要跟指向变量的类型保持一致

③指针变量占用的大小,跟数据类型无关,跟编译器有关(32位:4字节;64位:8字节)

④给指针赋值的时候,不能把一个数值赋值给指针变量

#include<stdio.h>
int main()
{
	//利用指针去获取变量中的数据/存储数据
	int a = 10;
	//定义指针去指向变量a
	int* p = &a;
	//利用指针去获取变量中的数据
	printf("%d\n", *p);
	//利用存储数据/修改数据
	*p = 200;
	printf("%d\n", *p);
	printf("%d\n", a);
	char c = 'a';
	char* p1 = &c;
	long long n = 200;
	long long* p2 = &n;
	printf("%zu\n", sizeof(p1));
	printf("%zu\n", sizeof(p2));
	return 0;
}

(1)作用

1:操作其他函数中的变量

#include<stdio.h>
void swap(int*p1, int*p2);
int main()
{
	/*定义两个变量,要求交换变量中记录的值
	注意:交换的代码写在一个新的函数swap中*/
	int a = 10;
	int b = 20;
	printf("调用前:%d,%d", a, b);
	swap(&a, &b);
	printf("调用后:%d,%d", a, b);
	return 0;
}
void swap(int*p1, int*p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

函数中变量的周期跟函数有关,函数结束了,变量也会消失

此时在其他函数中,就无法通过指针使用了

如果不想函数中的变量被回收,可以在变量前加static关键字 

2:函数返回多个值

#include<stdio.h>
int* method();
int main()
{
	int* p = method();
	printf("拖点时间\n");
	printf("%d\n", *p);
	return 0;
}
int* method()
{
	static int a = 10;
	return &a;
}

#include<stdio.h>
void getMaxAndMin(int arr[], int len, int* max, int* min);
int main()
{
	/*求数组的最大值和最小值,并返回*/
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int len = sizeof(arr) / sizeof(int);
	//调用getMaxAndMin函数
	int max = arr[0];
	int min = arr[0];
	getMaxAndMin(arr,len,&max,&min);
	printf("数组最大值为:%d\n", max);
	printf("数组最小值为:%d\n", min);
	return 0;

}
void getMaxAndMin(int arr[], int len,int*max,int*min)
{
	*max = arr[0];
	for (int i = 1;i < len;i++)
	{
		if (arr[i] > *max)
		{
			*max = arr[i];
		}
	}
	*min = arr[0];
	{
		for (int i = 1;i < len;i++)
		{
			if (arr[i]<*min)
			{
				*min = arr[i];
			}
		}
	}

}
	

3:函数的结果和计算

#include<stdio.h>
int getRemainder(int num, int num2, int* res);
int main()
{
	/*两数相除取余*/
	int a = 10;
	int b= 3;
	int res = 0;
	int flag=getRemainder(a, b, &res);
	if (!flag)
	{
		printf("获取到的余数为:%d\n", res);
	}
	return 0;
}
int getRemainder(int num1, int num2, int* res)
{
	if (num2 == 0)
	{
		return 1;
	}
	*res = num1 % num2;
	return 0;
}

4:方便的操作数组和函数

(2)指针的计算

步长:指针移动一次的字节数

#include<stdio.h>
int main()
{
	/*指针运算
	步长:指针移动一次的字节数
	char:1
	short:2
	int:4
	long:4
	long long:8
	加法:指针往后移动了N步p+1
	减法:指针往前移动了N步p-1*/
	int a = 10;
	int* p = &a;
	printf("%p\n", p);
	printf("%p\n", p+1);
	printf("%p\n", p+2);
	return 0;
}

有意义的操作:指针跟整数进行加、减操作(每次移动N个步长)

指针跟指针进行减操作(间隔步长)

无意义的操作:指针跟整数进行乘除操作

原因:此时指针指向不明

指针跟指针进行加、乘、除操作

#include<stdio.h>
int main()
{
	//保证空间内存连续
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 =&arr[0];
	printf("%d\n", *p1);
	printf("%d\n", *(p1+1));
	int* p2 = &arr[5];
	printf("%d\n", p2 - p1);
	printf("%p\n", p1);
	printf("%p\n", p2);
	return 0;
}

 

(3)野指针和悬空指针 

野指针:指针指向的空间未分配

悬空指针:指针指向的空间已分配,但是被释放了

#include<stdio.h>
int* method();
int main()
{
	/*野指针:指针指向的空间未分配
     悬空指针:指针指向的空间已分配,但是被释放了*/
	int a = 10;
	int* p1 = &a;
	printf("%p\n", p1);
	printf("%d\n",*p1);
	//p2野指针
	int*p2=p1 + 10;
	printf("%p\n", p2);
	printf("%d\n", *p2);
	//悬空指针:指针指向的空间已分配,但是被释放了
	int*p3=method();
	printf("拖点时间\n");
	printf("%p\n", p3);
	printf("%p\n", *p3);
	return 0;
}
int* method()
{
	int num = 10;
	int* p = &num;
	return p;
}

 (4)void类型的指针

特殊类型:

void *p 不代表任何类型

特点:无法获取数据,无法计算,但是可以接受任意地址 

#include<stdio.h>
void swap(void* p1, void* p2, int len);
int main()
{
	/*void类型的指针*/
	//1.定义两个变量
	int a = 10;
	short b = 20;
	//定义两个指针
	int* p1 = &a;
	short* p2 = &b;
	//3.输出打印
	printf("%d\n", *p1);
	printf("%d\n", *p2);
	//不同类型的指针之间,是不能相互赋值的
	//void类型的指针打破上面的观念
	//void没有任何类型,好处可以接受任意类型指针记录的内存地址
	void* p3 = p1;
	void* p4 = p2;
	//缺点:void类型的指针,无法获取变量里面的数据,也不能进行加、减的运算
	//调用函数交换函数
	int c = 100;
	int d = 200;
	swap(&c, &d, 4);
	printf("c=%d,d=%d\n",c,d);
	return 0;
}
//函数:用来交换两个变量记录的数据
//修改一下函数,更具有通用性
void swap(void *p1, void *p2, int len)
{
	//把void类型的指针,转成char类型的指针
	char* pc1 =(char*) p1;
	char* pc2 = (char*)p2;
	char temp = 0;
	//以字节为单位,一个字节一个字节的进行交换
	for (int i = 0;i < len;i++)
	{
		temp = *pc1;
		*pc1=*pc2;
		*pc2=temp;
		pc1++;
		pc2++;
	}
}

 (4)二级指针和多级指针

 指针数据类型:跟指向空间中,数据的类型保持一致的

二级指针

int**指针名

作用:可以操作一级指针记录的地址 

#include<stdio.h>
int main()
{
	/*二级指针
	格式:数据类型**指针名
	*/
	//定义变量
	int a = 10;
	int b= 20;
	//定义一级指针
	int* p = &a;
	//定义二级指针
	int** pp = &p;
	//作用一:利用二级指针修改一级指针里面记录的内存地址
	*pp = &b;
	//作用二:利用二级指针获取到变量中记录的数据
	//输出打印
	printf("%p\n", &a);
	printf("%p\n", &b);
	printf("%p\n", p);
	printf("%d\n", **pp);
	return 0;
}

(5)数组指针的基本用法

概念:指向数组的指针,叫做数组指针

作用:方便操作数组中的各种数据 

#include<stdio.h>
int main()
{
	/*利用指针遍历数组*/
	//1.定义数组
	int arr[] = { 10,20,30,40,50 };
	int len = sizeof(arr) / sizeof(int);
	//2.获取数组的指针
	//实际上获取的:数组的首地址
	int* p1 = arr;
	//int* p2 = &arr[0];
	//3.利用循环和指针遍历数组获取里面每一个元素
	for (int i = 0; i < len; i++)
	{
		printf("%d\n", *p1++);
	}
	return 0;
}

细节 arr参与计算的时候,会退化为第一个元素的指针

特殊情况:sizeof运算的时候,不会退化,arr还是整体

&arr获取地址地址的时候,不会退化

#include<stdio.h>
int main()
{
	/*练习:指针的细节
	 arr参与计算的时候,会退化为第一个元素的指针
     特殊情况:sizeof运算的时候,不会退化,arr还是整体
      &arr获取地址地址的时候,不会退化*/
	//1.定义数组
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//2.sizeof运算的时候,不会退化,arr还是整体
	printf("%zu\n", sizeof(arr));
	//3.&arr获取地址地址的时候,不会退化,记录的内存地址是数组的首地址,步长:数据类型*数组的长度
	//arr参与计算的时候,会退化为第一个元素的指针,记录的内存地址是数组的首地址,步长:数据类型int 4
	printf("%p\n", arr);
	printf("%p\n", &arr);
	printf("%p\n", arr+1);//4
	printf("%p\n", &arr+1);//40
	return 0;
}

概念:把多个小数组,放在一个大的数组中

数据类型 arr[m][n]=

{                                           m:二维数组的长度

  {1,2,3,4...},                         n:以为数组的长度

  {1,2,3,4...},

  {1,2,3,4...},

}; 

#include<stdio.h>
int main()
{
	/*二维数组
	定义格式一:arr[m][n]=
        m:二维数组的长度
		n:一维数组的长度
		遍历:利用索引进行遍历/利用指针进行遍历*/
	//1.定义一个二维数组
	int arr[3][5] =
	{
		{1,2,3,4,5},
		{11,22,33,44,55},
		{111,222,333,444,555}
	};
	//2.利用索引进行遍历
	//arr[0]:表示二维数组当中的第一个一位数组,{1,2,3,4,5}
	//arr[1]:表示二维数组当中的第二个一位数组,{11,22,33,44,55}
	//arr[2]:表示二维数组当中的第三个一位数组,{111,222,333,444,555}
	for (int i = 0; i < 3; i++)
	{
		//i:依次表示二维数组中的索引
		for (int j = 0; j < 5; j++)
		{
			//j:依次表示一维数组中的索引
			//内循环:遍历每一个一位数组
			printf("%d ", arr[i][j]);
		}
		//当内循环结束,表示一维数组遍历完毕
		printf("\n");
	}
	return 0;
}

#include<stdio.h>
int main()
{
	/*二维数组的定义格式+索引遍历
	核心:实现先把所有的一维数组定义完毕,再放入到二维数组当中*/
	//1.定义三个一维数组
	int arr1[3] = { 1,2,3 };
	int arr2[5] = { 1,2,3,4,5 };
	int arr3[9] = { 1,2,3,4,5,6,7,8,9 };
	//预先计算每个数组长度
	int len1 = sizeof(arr1) / sizeof(int);
	int len2 = sizeof(arr2) / sizeof(int);
	int len3 = sizeof(arr3) / sizeof(int);
	//再定义一个数组,装所有数组的长度
	int lenArr[3] = { len1,len2,len3 };
	//2.把三个一维数组放入到二维数组当中
	//数组的数据类型,跟内部存储的元素类型保持一致
	//arr1:使用数组名进行计算的时候,退化为指向第一个元素的指针,此时不再表示数组的那个整体了
	//指针---内存地址   64位win 8个字节
	int* arr[3] = { arr1,arr2,arr3 };
	//3.利用索引遍历arr
	for (int i = 0; i < 3; i++)
	{
		//i:依次表示二维数组的索引0 1 2
		int len = sizeof(arr[i])/sizeof(int);
		for (int j = 0; j <lenArr[i] ; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

#include<stdio.h>
int main()
{
	/*二维数组
	利用指针遍历二维数组*/
	//1.定义一个二维数组
	int arr[3][5] =
	{
		{1,2,3,4,5},
		{11,22,33,44,55},
		{111,222,333,444,555}
	};
	//2.利用指针遍历二维数组
	//获取二维数组指针
	//数组指针的数据类型:要跟数组内部元素的类型保持一致
	int(* p)[5] = arr;
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(*p + j));
		}
		//换行
		printf("\n");
		//移动二维数组的指针,继续遍历下一个一维数组
		p++;
	}
	return 0;
}

#include<stdio.h>
int main()
{
	/*二维数组
	利用指针遍历二维数组*/
	//1.定义一个二维数组
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5]={11, 22, 33, 44, 55};
	int arr3[5] = {111, 222, 333, 444, 555};
	//2.把三个一位数组的内存地址,再放入到二维数组当中
	int*arr[3] = {arr1,arr2,arr3};
	//3.获取指针
	int** p = arr;
	//4.遍历数组
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(*p+j));
		}
		//换行
		printf("\n");
		//移动指针
		p++;
	}
	return 0;
}

(6)数组指针和指针数组

数组指针:指向数组的指针

作用:方便的操作数组中的各种数据

指针数组:存放指针的数组

(7)函数指针

格式:返回值类型(*指针名)(形参列表)

作用:利用函数指针,可以动态的调用函数

#include<stdio.h>
void method1();
int method2(int num1, int num2);
int main()
{
	/*函数指针*/
	//1.定义指针指向两个函数
	void (*p1)() = method1;
	int (*p2)(int, int) = method2;
	//2.利用函数指针去调用函数
	p1();
	int num=p2(10,20);
	printf("%d\n", num);
	return 0;
}
void method1()
{
	printf("method1\n");
}
int method2(int num1,int num2)
{
	printf("method2\n");
	return num1 + num2;
}

练习

#include<stdio.h>
int add(int num1, int num2);
int subtract(int num1, int num2);
int mutiply(int num1, int num2);
int divide(int num1, int num2);
int main()
{
	/*
	定义加、减、乘、除四个函数
	用户键盘录入三个数字
	前两个表示参与计算的数字
	第三个数字表示调用的函数
	1:加法
	2:减法
	3:乘法
	4:除法*/
	//1.定义一个数组去装四个函数的指针
	//函数指针数组
	int(*arr[4])(int, int) = { add,subtract,mutiply,divide };
	//2.用户键盘录入三个数据
	printf("请录入两个数字参与计算\n");
	int num1;
	int num2;
	scanf_s("%d%d", &num1,&num2);
	printf("%d\n",num1);
	printf("%d\n",num2);
	int choose;
	printf("请录入一个数字表示要进行的计算\n");
	scanf_s("%d", &choose);
	//3.根据用户选择,来调用不同的函数
	int res=(arr[choose - 1])(num1,num2);
	//4.输出打印
	printf("%d\n", res);
	return 0;
}
int add(int num1,int num2)
{
	return num1 + num2;
}
int subtract(int num1, int num2)
{
	return num1 - num2;
}
int mutiply(int num1, int num2)
{
	return num1 *num2;
}
int divide(int num1, int num2)
{
	return num1 / num2;
}

细节:只有形参完全相同而且返回值也要一样的函数,才能放到同一个函数指针数组当中

九、字符串 

(1)字符串的输入和输出

#include<stdio.h>
int main()
{
	/*两种定义字符串的实现*/
	//1.利用字符数组+双引号的方式定义字符串
	char str1[4] = "abs";
	printf("%s\n", str1);
	//细节1:在底层,实际存储的时候,C语言还是会帮我们把字符串“abc”转换成字符数组进行保存,
	// 并且在末尾还要加上'\0'
	//细节2:数组的长度,要么不写,如果要写的话,记得把结束标记的空间给预留出来
	//细节3:字符数组+双引号的方式定义字符串,内容是可以发生改变的
	str1[0] = 'Q';
	printf("%s\n", str1);
	//2.利用指针+双引号的方式定义字符串
	char* str2 = (char*)"abcd";
	printf("%s\n", str2);
	//细节1:在底层,实际存储的时候,C语言还是会帮我们把字符串“abcd”转换成字符数组进行保存,
	// 并且在末尾还要加上'\0'
	//细节2:利用指针+双引号的方式定义字符串,会把底层的字符数组放在只读常量区
	//只读常量区特点:内容不可以修改的、里面定义的字符串是可以复用的
	//在创建abcd的时候,会检查只读常量区里面有没有abcd,如果没有才会创建新的,
	// 如果已经有了,不会创建新的,而是进行复位
	return 0;
}

#include <stdio.h>
int main()
{
    /*键盘录入一个字符串,使用程序实现在控制台遍历该字符串*/
    // 1.键盘录入字符串
    char str[100];
    printf("请录入一个字符串\n");
    scanf("%s", str);
    printf("接收到的字符串为:%s\n", str);

    // 2.遍历字符串得到每一个字符
    char* p = str;
    while (1)
    {
        // 利用指针获取字符串中的每一个字符,直到遇到 '\0' 为止
        char c = *p;

        // 判断当前获取到的字符是否为结束标记
        if (c == '\0')
        {
            // 如果是结束标记,循环结束
            break;
        }

        // 打印当前遍历到的字符
        printf("%c\n", c);

        // 指针往后移动一个位置
        p++;
    }

    return 0;
}

(2)字符串数组

#include <stdio.h>
int main()
{
    /*需求:定义一个数组存储5个学生的名字并进行遍历
    字符串的底层就是字符数组
    把多个字符数组,再放入到一个大的数组当中
    二维数组*/
    //1.定义一个二维数组存储多个学生的名字
    char strArr[5][100] =
    {
        "zh",
        "qi",
        "sun",
        "li",
        "wu"
    };
    //2.遍历二维数组
    for (int i = 0; i < 5; i++)
    {
        //i:依次表示二维数组中的每一个索引
        char* str = strArr[i];
        printf("%s\n", str);
    }
    //第二种方式
    //把五个字符的指针,放入到一个数组当中
    //指针数组
    const char* strArr2[5] =
    {
        "zh",
        "qi",
        "sun",
        "li",
        "wu"
    };
    //遍历指针数组
    for (int i = 0;i < 5;i++)
    {
        //i:依次表示二维数组中的每一个索引
        const char* str = strArr2[i];
        printf("%s\n", str);
    }
    return 0;
}

(3)string常见函数

strlen:获取字符串的长度

strcat:拼接两个字符串

strcpy:复制字符串

strcmp:比较两个字符串

strlwr:将字符串变成小写

strupr:将字符串变成大写

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h>
int main()
{
    const char* str1 = "abc";//底层会把字符数组放在只读常量区(只能读,不能修改、复用)
    char str2[100] = "abc";
    char str3[5] = { 'q','w','e','r','\0' };
    printf("-----------------strlen(长度)----------------------\n");
    //细节1:strlen这个函数在统计长度的时候,不计算结束标记
    //细节2;在windows中,默认情况下,一个中文占两个字节
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int len3= strlen(str3);
    printf("%d\n", len1);
    printf("%d\n", len2);
    printf("%d\n", len3);
    printf("-----------------strcat(拼接)----------------------\n");
    //细节1:把第二个字符串中全部内容,拷贝到第一个字符串末尾
    //前提1:第一个字符串可以修改
    //前提2:第一个字符串中剩余的空间可以容纳拼接的字符串
    strcat(str2, str3);
    printf("%s\n", str2);
    printf("%s\n", str3);
    printf("-----------------strcpy(拷贝)----------------------\n");
    strcpy(str2, str3);
    //细节:把第二个字符串中全部内容,拷贝到第一个字符串末尾,把第一个字符串里面原有的内容给覆盖了
    //前提1:第一个字符串可以修改
    //前提2:第一个字符串中所有的空间可以容纳拼接的字符串
    printf("%s\n", str2);
    printf("%s\n", str3);
    printf("-----------------strcmp(比较)----------------------\n");
    //一样:0;不一样:非0
    int res = strcmp(str1, str2);
    printf("%d\n", res);
    printf("-----------------strlwr(变小写)----------------------\n");
    _strlwr(str2);
    printf("%s\n", str2);
    //只能转换英文,不能转换中文
    printf("-----------------strupr(变大写)----------------------\n");
    _strupr(str2);
    printf("%s\n", str2);
    return 0;
}

练习1-用户登录

#include<stdio.h>
#include<string.h>
int main()
{
	/*已知正确的用户名和密码,用程序实现模拟用户登录
	三次机会,登录后,给出相应提示*/
	const char* rightUsername = "zh";
    const char* rightPassword = "123zh";
	//2.键盘录入两个字符串,表示用户输入的用户名和密码
	
	//3.比较
	for (int i = 0; i <= 3; i++)
	{
		printf("请输入用户名\n");
		char username[100];
		scanf("%s", username);
		printf("请输入密码\n");
		char password[100];
		scanf("%s", password);
		printf("%s\n", username);
		printf("%s\n", password);
		if (!strcmp(username, rightUsername) && !strcmp(password, rightPassword))
		{
			printf("登陆成功\n");
			break;
		}
		else
		{
			if (i == 3)
			{
				printf("用户%s被锁定\n", username);
			}
			else
			{
				printf("登陆失败,还剩下%d次机会\n", 3 - i);
			}
		}
	}
}

练习2-统计次数

#include<stdio.h>
#include<string.h>
int main()
{
	/*统计字符串中大写字母字符,小写字母字符,数字字符出现的次数*/
	//1.键盘录入一个字符串
	printf("请输入一个字符串\n");
	char str[100];
	scanf("%s", str);
	//2.统计该字符中大写字母字符,小写字母字符,数字字符出现的次数
	//遍历字符串得到里面每一个字符
	int bigCount = 0;
	int smallCount = 0;
	int numberCount = 0;
	for (int i = 0; i < strlen(str); i++)
	{
		char c = str[i];
		if (c >= 'a' && c <= 'z')
		{
			smallCount++;
		}
		else if (c >= 'A' && c <= 'Z')
		{
			bigCount++;
		}
		else if (c >= '0' && c <= '9')
		{
			numberCount++;
		}
	}
	printf("大写字符出现了%d次\n", bigCount);
	printf("小写字符出现了%d次\n", smallCount);
	printf("数字字符出现了%d次\n", numberCount);
	return 0;
}

十、结构体

(1)定义

结构体可以理解为自定义的数据类型

由一批数据组合而成的结构体数据

#include<stdio.h>
#include<string.h>
/*结构体:自定义的数据类型
由很多的数据组合成的一个整体
每一个数据,都是结构体的成员
书写的位置:
函数里面:局部位置,只能在本函数中使用
函数外面:全局位置,在所有函数中都可以使用*/
struct GirlFriend
{
	char name[100];
	int age;
	char gender;
	double height;
};
int main()
{
	//使用结构体
	//定义一个女朋友类型的变量
	struct GirlFriend gf1;
	strcpy(gf1.name, "aaa");
	gf1.age = 23;
	gf1.gender = 'F';
	gf1.height = 1.64;
	//输出定义
	printf("女朋友的名字为:%s\n", gf1.name);
	printf("女朋友的年龄为:%d\n", gf1.age);
	printf("女朋友的性别为:%c\n", gf1.gender);
	printf("女朋友的身高为:%lf\n", gf1.height);
	struct GirlFriend gf2;
	strcpy(gf2.name, "aaax");
	gf2.age = 24;
	gf2.gender = 'F';
	gf2.height = 1.67;
	//输出定义
	printf("女朋友的名字为:%s\n", gf2.name);
	printf("女朋友的年龄为:%d\n", gf2.age);
	printf("女朋友的性别为:%c\n", gf2.gender);
	printf("女朋友的身高为:%lf\n", gf2.height);

}

(2) 结构体数组

#include<stdio.h>
struct Student
{
	char name[100];
	int age;
};
int main()
{
	//1.定义三个学生, 同时并进行赋值
	struct Student stu1 = { "zhangsan",23 };
	struct Student stu2 = { "lisi",24 };
	struct Student stu3 = { "wangwu",25 };
	//2.把三个学生放入到数组当中
	struct Student stuArr[3] = { stu1 ,stu2, stu3 };
	//3.遍历数组得到每一个元素
	for (int i = 0; i < 3; i++)
	{
		struct Student temp = stuArr[i];
		printf("学生的信息为: 姓名%s, 年龄%d\n", temp.name, temp.age);
	}
	return 0;
}

(3) 起别名

#include<stdio.h>
typedef struct
{
	char name[100];
	int attack;
	int defense;
	int blood;
}M;
int main()
{
	/*
	定义一个结构体表示游戏人物
	属性有: 姓名、攻击力、防御力、血量
	要求: 把三个游戏人物放入到数组当中, 并遍历数组
	*/
	//1.定义三个奥特曼
	M taro = { "泰罗", 100, 90, 500 };
	M rem = { " 雷欧", 90, 80, 450 };
	M eddie = { "艾迪", 120, 70, 600 };
	//2.定义数组
	M arr[3] = { taro, rem, eddie };
	//3.遍历数组
	for (int i = 0; i < 3; i++)
	{
		M temp = arr[i];
		printf("奥特曼的名字为%s, 攻击力是%d, 防御力是%d, 血量是%d\n", temp.name, temp.attack, temp.defense, temp.blood);
	}
	return 0;
}

(4)结构体作为函数参数 

#include<stdio.h>
#include<string.h>
typedef struct Student
{
	char name[100];
	int age;
}S;
//细节: 因为这个函数用到了结构体,所以函数的申明必须写在结构体的下面,否则代码会报错
void method(S st);
void method2(S* p);
int main()
{
	/*
	定义一个结构体表示学生
	学生的属性: 姓名、年龄
	要求:
	定义一个函数, 修改学生中的数据
	*/
	//1.定义一个学生
	S stu;
	//2.给学生赋初始值
	strcpy(stu.name, "aaa");
	stu.age = 0;
	//3.输出打印
	printf("学生的初始数据为: %s, %d\n", stu.name, stu.age);
	//4.调用函数修改学生中的数据
	method2(&stu);
	//5.输出打印
	printf("学生的信息修改为: %s, %d\n", stu.name, stu.age); 
}
//细节:
//如果函数中写的是结构体类型的变量, 相当于是定义了一个新的变量
//此时是把 main函数中 stu中的数据,传递给了 method函数,并把 stu中的数据赋值给了新的变量st
//我们在函数中,仅仅是修改了变量 st中的值,对 main函数中stu的值,是没有进行修改的
void method(S st)
{
	printf("接收到 main函数中学生的初始数据为: %s, %d\n", st.name, st.age);
	//修改
	printf("请输入要修改的学生名字\n");
	scanf("%s", st.name);
	printf("请输入要修改的学生年龄\n");
	scanf("%d", &(st.name));
	printf("在method函数中修改之后,学生的信息为: %s, %d\n", st.name, st.age);
}
//如果要在函数中修改 stu的值,此时就不要再定义新的变量了
//直接接收 stu的内存地址, 通过内存地址就可以修改 stu中的数据了
// 指针p里面记录的是 main函数中 stu的内存地址 ( stu 学生)
void method2(S* p)
{
	printf("接收到 main函数中学生的初始数据为: %s, %d\n", (*p).name, (*p).age);// aaa 0
	//修改
	printf("请输入要修改的学生名字\n");
	scanf("%s", (*p).name);
	printf("请输入要修改的学生年龄\n");
	scanf("%d", &((*p).age));
	printf("在method函数中修改之后, 学生的信息为: %s, %d\n", (*p).name, (*p).age);// zhangsan 23
}

(5)结构体嵌套

#include<stdio.h>
#include<string.h>
struct Message
{
	char phone[12];
	char mail[100];
};
struct Student
{
	char name[100];
    int age;
    char gender;
    double height;
    struct Message msg;
};
int main()
{
	/*定义一个结构体表示学生 Student
		Student成员如下 :
	名字、年龄、性别、身高、联系方式
		联系方式 Message也是一个结构体, 成员如下:
		手机号、电子邮箱
	*/
	//1. 定义学生类型的变量
	struct Student stu;
	//2.给里面的每一个成员进行赋值
	strcpy(stu.name, "zhangsan");
	stu.age = 23;
	stu.gender = 'M';
	stu.height = 1.78;
	strcpy(stu.msg.phone, "13112345678");
	strcpy(stu.msg.mail,"12345678@qq.com");
	//3.输出打印
	printf("学生的信息为: \n");
	printf("姓名为: %s\n", stu.name);
	printf("年龄为: %d\n", stu.age);
	printf("性别为: %c\n", stu.gender);
	printf("身高为: % lf\n", stu.height);
	printf("手机号为: %s\n", stu.msg.phone);
	printf("邮箱为: %s\n", stu.msg.mail);
	printf("-------------------------------\n");
	//批量进行赋值
	struct Student stu2 = { "lisi",24,'F',1.65, {"13112347890","5678@qq. com"}};
	//3.输出打印
	printf("学生的信息为: \n");
	printf("姓名为: %s\n", stu2.name);
	printf("年龄为: %d\n", stu2.age);
	printf("性别为: %c\n", stu2.gender); 
		printf("身高为: % lf\n", stu2.height);
	printf("手机号为: %s\n", stu2.msg.phone);
	printf("邮箱为: %s\n", stu2.msg.mail);
	return 0;
}

#include<string.h>
struct spot
{;
	char name[100];
    int count;
};
int main()
{
	/*
	某班级组织野外郊游,想要在ABCD四个景点选择其中一个。
	现在班上有80名同学进行投票,找出投票数最多的景点
	 Ps:
	1.学生投票,用随机数模拟
	2.如果多个景点投票一样的话,A优先B,B优先于C,C优先于D
	*/
	// 1.定义数组存储4个spot类型的变量
	struct spot arr[4] = { {"A",0}, {"B",0}, {"C",0}, {"D",0}};
	//2.模拟80名同学的投票
	srand(time(NULL));
	for (int i = 0; i < 80; i++)
	{
		// choose变量有两个含义
		//含义一:表示用户的投票 0 A 1 B 2 C 3 D
		//含义二:表示arr中的索引,通过这个索引就可以获取到景点的名字和投票数量 
		int choose = rand() % 4; // 0 1 2 3
		//choose:表示同学的投票, 同时也表示数组中的索引
		// arr[ choose]: 表示获取景点的信息 (名字, 数量)
		// arr[ choose]. count: 表示这个景点已经投了多少票
		// arr[ choose]. count++: 给这个景点再投一票
		arr[choose].count++;
	}
	//找最大值
	int max = arr[0].count;
	for (int i = 1; i < 4; i++)
	{
		struct spot temp = arr[i];
		if (temp.count > max)
		{
			max = temp.count;
		}
	}
		//遍历数组, 看谁的票数刚好是最大值
		for (int i = 1; i < 4; i++)
		{
			struct spot temp = arr[i];
			if (temp.count == max)
			{
				max = temp.count;
			}
		}
		//遍历数组,看谁的票数刚好是最大值
		for (int i = 0;i < 4;i++)
		{
			struct spot temp = arr[i];
			if (temp.count == max)
			{
				printf("投票最多的景点为: %s, 共计: %d张票\n", temp.name, temp.count);
				break;
			}
		}
		//遍历
		for (int i = 0; i < 4; i++)
		{
			struct spot temp = arr[i];
			printf("%s %d\n", temp.name, temp.count);
		}
		return 0;
}

(6)内存对齐

确定变量位置:只能放在自己类型整数倍的内存地址上

最后一个补位:结构体的总大小,是最大类型的整数倍

#include<stdio.h>
/*内存对齐:
不管是结构体, 还是普通的变量都存在内存对齐
规则:
只能放在自己类型整数倍的内存地址上
简单理解:
内存地址/占用字节 = 结果可以整除
举例:
int存放的位置:内存地址一定能被4整除
long long存放的位置:内存地址一定能被8整除
double存放的位置:内存地址一定能被8整除
结构体的内存对齐:
结构体在上面的基础上又多了一条,结构体的总大小,是最大类型的整数倍(用来确定最后一个数据补位的情况)
切记!!
对齐的时候会补空白字节,但是不会改变原本字节的大小,char补位之后,本身还是1个字节
心得:把小的数据类型放在最上面,大的数据类型放在最下面(节约空间)
*/
struct Num
{
    char b;
    char d;
    int c;
	double a;
};
int main() 
{
    struct Num n;
    printf("%zu\n", sizeof(n));
    return 0;
}

十一、共同体 

核心:一种数据可能有多个数据

#include<stdio.h>
#include<string.h>
union MoneyType
{
	int moneyi;
	double moneyd;
	char moneystr[100];
};
int main()
{
	/*
	需求:
	金融项目中,钱有可能是整数,小数,字符串,请定义对应的共同体
	*/
	//1.利用共同体定义钱的变量
	union MoneyType money;
	// 2.赋值
	// 整数 moneyi、小数 moneyd、字符串 moneystr
	//而且每次只能赋一个值
	// money. moneyi = 99999;
    // money. moneyd = 123.32;
	strcpy(money.moneystr, "100万");
	//3.输出打印
	//上面赋值给哪个类型,下面就从哪个类型取出来
	// printf("%d\n", money. moneyi);
	// printf("%1f\n", money. moneyd);
	printf("%s\n", money.moneystr);
	return 0;
}

(1)特点

  • 共用体,也叫联合体,共同体
  • 所有的变量都使用同一个内存空间
  • 所占的内存大小=最大成员的长度 (也受内存对齐影响)
  • 每次只能给一个变量进行赋值, 因为第二次赋值时会覆盖原有的数据
#include<stdio.h>
#include<string.h>
union MoneyType
{
	int moneyi;
	double moneyd;
	char moneystr[100];
};
int main()
{
	/*特点:
	1.共用体,也叫联合体,共同体
    2.所有的变量都使用同一个内存空间
    3.所占的内存大小=最大成员的长度 (也受内存对齐影响)
	细节:怎么存就怎么取出来
    4.每次只能给一个变量进行赋值, 因为第二次赋值时会覆盖原有的数据
	细节:以最大的单个成员的长度为准
	总大小一定是最大单个成员的整数倍*/
	//1.利用共同体定义钱的变量
	union MoneyType money;
	//2.获取内存地址
	printf("%p\n", &(money.moneyi));
	printf("%p\n", &(money.moneyd));
	printf("%p\n", &(money.moneystr));
	printf("%zu\n", sizeof(money.moneyi));// 4
	printf("%zu\n", sizeof(money.moneyd));// 8
	printf("%zu\n", sizeof(money.moneystr));// 100
	printf("%zu\n", sizeof(money));// 104 (后面会补4个空白字节)
	money.moneyi = 99;
	money.moneyd = 1.23;
	printf("%lf\n",money.moneyd);
	return 0;
}

 (2)结构体和共用体的区别

结构体:一种事物中包含多个属性

共用体:一个属性有多个类型

  • 存储方式:
  • 结构体:各存各的
  • 共用体:存一起,多次存会覆盖
  • 内存占用:
  • 结构体:各个变量的总和 (受内存对齐影响)
  • 共用体:最大类型 (受内存对齐影响)

十二、动态内存分配

(1)常用函数

函数名全部单词作用
 mallocmemory allocation申请空间(连续)
 calloccontiguous allocation申请空间+数据初始化
 realloc re-allocation修改空间大小
 free free释放空间
#include <stdio.h>
#include <stdlib.h>
int main()
{
    /*
      malloc 申请空间(连续)
      calloc 申请空间 + 数据初始化
      realloc 修改空间大小
      free  释放空间
      stdlib.h
    */
    // 1.利用 malloc 函数申请一片连续的空间
    // 需求:申请一片空间,要存储 10 个 int 类型的整数
    // 返回这片空间的首地址
    int* p = (int*)malloc(10 * sizeof(int));
    // int* p = calloc(10, sizeof(int));
    // printf("%p\n", p);
    // 2.赋值
    for (int i = 0; i < 10; i++)
    {
        // 第一种赋值
        // *(p + i) = (i + 1) * 10; //10 20 30 40 50 60 70 80 90 100
        // 第二种赋值
        p[i] = (i + 1) * 10;
        // p[i] - - - > p + i
    }
    // 4.扩容,20 个 int 类型的整数
    int* pp = (int*)realloc(p, 20 * sizeof(int));
    // 3.遍历
    for (int i = 0; i < 20; i++)
    {
        // printf("%d", *(p + i));
        printf("%d ", p[i]);
    }
    //5.释放空间
    //如果申请的空间不需要再继续使用,那么记得一定要释放
    free(pp);
    return 0;
}

(2)细节 

1.malloc创建空间的单位是字节
2.malloc返回的是void类型的指针,没有步长的概念也无法获取空间中的数据, 需要强转
3.malloc返回的仅仅是首地址,没有总大小,最好定义一个变量记录总大小
4.malloc申请的空间不会自动消失, 如果不能正确释放,会导致内存泄露
5.malloc申请的空间过多,会产生虚拟内存
6.malloc申请的空间没有初始化值,需要先赋值才能使用
7.free释放完空间之后, 空间中数据叫做脏数据,可能被清空,可能被修改为其他值
8.calloc就是在malloc的基础上多一个初始化的动作
9.realloc修改之后的空间,地址值有可能发生变化,也有可能不会改变,但是原本的数据不会丢失
10.realloc修改之后,无需释放原来的空间,函数底层会进行处理

#include <stdio.h>
#include <stdlib.h>
void method(int* p, int size);
int main()
{
    /*1.malloc创建空间的单位是字节
        2.malloc返回的是void类型的指针,没有步长的概念,也无法获取空间中的数据,需要强转
        3.malloc返回的仅仅是首地址,没有总大小,最好定义一个变量记录总大小
        4.malloc申请的空间不会自动消失,如果不能正确释放, 会导致内存泄露
        5.malloc申请的空间过多,会产生虚拟内存
        6.malloc申请的空间没有初始化值,需要先赋值才能使用
        7.free释放完空间之后,空间中数据叫做脏数据,可能被清空,可能被修改为其他值
        8.calloc就是在malloc的基础上多一个初始化的动作
        9.realloc修改之后的空间,地址值有可能发生变化, 也有可能不会改变,但是原本的数据不会丢失
        10.realloc修改之后,无需释放原来的空间,函数底层会进行处理*/
        //通用性
   // int*: 指针的步长
   //p: 首地址
    int* p = (int*)malloc(25 * sizeof(int));
    int size = 25;
    method(p, 25);
    free(p);
    return 0;
}
//遍历
void method(int* p, int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("%d", p[i]);
    }
    printf("\n");
}

#include <stdio.h>
#include <stdlib.h>
int main()
{
	// 5, malloc申请的空间过多, 会产生虚拟内存
//表示单次申请空间的字节大小(1G)
	int number = 1024 * 1024 * 1024;
	//利用循环不断地申请空间
	// malloc 申请空间 (连续) 
	//如果申请空间成功,返回这个空间的首地址
	//如果申请空间失败,返回NULL
	int count = 0;
	while (1)
	{
		int* p = (int*)malloc(number);
		count++;
		if (p == NULL)
		{
			printf("申请失败");
			break;
		}
		printf("内存%d申请成功%p\n",count, p);
	}
	return 0;
}

# include<stdio.h>
# include<stdlib.h>
int main()
{
	/*malloc 申请空间(连续)
		free 释放空间
		calloc 申请空间 + 数据初始化
		realloc 修改空间大小
		7.free释放完空间之后,空间中数据叫做脏数据,可能被清空,可能被修改为其他值
		8.calloc就是在malloc的基础上多一个初始化的动作
		*/
	//1.申请一片连续的空间存储10个int类型的整数
	int* p =(int*) malloc(10 * sizeof(int));
	int size = 10;
	//2.给这片空间赋值
	for (int i = 0; i < size; i++)
	{
		*(p + i) = (i + 1) * 10;
	}
	// 10 20 30 .... 100
	// 3.遍历
	printf("遍历空间中的数据为: \n");
	for (int i = 0; i < size; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
	//4.释放空间
	free(p);
	//5.释放之后再次遍历
	printf("释放之后再次遍历数据为: \n");
	for (int i = 0; i < size; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}
			

# include<stdio.h>
# include<stdlib.h>
int main()
{
	/*malloc 申请空间(连续)
		free 释放空间
		calloc 申请空间 + 数据初始化
		realloc 修改空间大小
		9.realloc修改之后的空间,地址值有可能发生变化,也有可能不会改变,但是原本的数据不会丢失
		如果内存中已经无法申请空间了,会返回NULL
        10.realloc修改之后,无需释放原来的空间,函数底层会进行处理
		*/
	//1.申请一片连续的空间存储10个int类型的整数
	int* p1 =(int*) malloc(10 * sizeof(int));
	printf("最初的内存地址为:%p\n", p1);
	int size = 10;
	//2.给这片空间赋值
	for (int i = 0; i < size; i++)
	{
		*(p1 + i) = (i + 1) * 10;
	}
	//3.修改大小
	//realloc修改之后,无需释放原来的空间,函数底层会进行处理
	//如果内存地址没变,底层在原来空间的后面接着申请的
	//如果内存地址不变,申请一个新的大的空间,把原来的数据拷贝到新的空间当中,再把原来的空间给free掉
	int* p2 =(int*) realloc(p1, 20* sizeof(int));
	printf("修改之后的内存地址为:%p\n", p2);
	size = 20;
	// 3.遍历
	printf("遍历空间中的数据为: \n");
	for (int i = 0; i < size; i++)
	{
		printf("%d ", *(p2 + i));
	}
	printf("\n");
	//如果我想释放空间
	free(p2);
	return 0;
}
			

 

(3)C语言的内存结构 

十三、文件 

(1)路径

文件在电脑中的位置

两种表示方式:绝对路径、相对路径

#include<stdio.h>
int main()
{
	/*绝对路径:
	以盘符开始,C:\aaa\a.txt
	相对路径:
	参照物:当前项目
	不以盘符开始,aaa\a.txt*/
	return 0;
}

(2)转义字符 

#include<stdio.h>
int main()
{
	/*绝对路径:
	以盘符开始,C:\aaa\a.txt
	相对路径:
	参照物:当前项目
	不以盘符开始,aaa\a.txt
	路径是以字符串的形式来表示的
	\:转义字符
	需求1:打印一个双引号
	“:字符串的开头或结尾
	此时,我要打印一个普普通通的双引号
	\:表示改变\后面这个符号原来的含义,把它变成一个普普通通的双引号
	需求2:打印一个\*/
	printf("\"");
	printf("\\");
	//1.定义字符串表示路径
	const char* file = "C:\\aaa\\a.txt";
	//2.输出打印
	printf("%s\n", file);
	return 0;
}

(3)fgetc 

读取数据:把本地文件中的数据读到程序中来

书写步骤:①打开文件fopen

②读数据 fgetc fgets fread

③关闭文件 fclose

#include<stdio.h>
int main()
{
	/*     打开文件: fopen
     写出数据: fgetc一次读一个字符, 读不到返回-1,
     fgets 一次读一行, 读不到返回 null
     fread 一次读多个
    关闭文件: fclose
     C:\a\aaa.txt*/
    //1.打开文件
    FILE*file=fopen("C:\\a\\aaa.txt","r");
    //2.读取数据fgetc一次读一个字符,读不到返回-1, 读到了就会返回读取到的字符
    int c;
    while ((c = fgetc(file)) != -1)
    {
        printf("%c", c);
   }
    //3.关闭文件
    fclose(file);
	return 0;
}

(4)fgets 

#include<stdio.h>
int main()
{
	/*     打开文件: fopen
     写出数据: fgetc一次读一个字符, 读不到返回-1,
     fgets 一次读一行, 读不到返回 null
     fread 一次读多个
    关闭文件: fclose
     C:\a\aaa.txt*/
    //1.打开文件
    FILE*file=fopen("C:\\a\\aaa.txt","r");
    //2.读取数据
    //一次读一行, 读不到返回 null,以换行符为准
    char arr[1024];
    char* str;
    while ((str = fgets(arr, 1024, file)) != NULL)
    {
        printf("%s", str);
    }
    //3.关闭文件
    fclose(file);
	return 0;
}

(5)利用fread一次读多个字节 

#include<stdio.h>
int main()
{
	/*     打开文件: fopen
     写出数据: fgetc一次读一个字符, 读不到返回-1,
     fgets 一次读一行, 读不到返回 null
     fread 一次读多个
    关闭文件: fclose
     C:\a\aaa.txt*/
    //1.打开文件
    FILE*file=fopen("C:\\a\\aaa.txt","r");
    //2.读取数据
    /*英文的 abc:占用1个字节
        中文 : window64位当中, 默认2个字节
        fread细节 :
    在读取的时候,每次尽可能会把数组给装满,返回当前读取到的有效字节个数
        文件:100个字节
        数组长度:30
        第一次:读取前面30个字节, 把数组给装满, 函数返回30
        第二次:读取后面30个字节, 把数组给装满, 函数返回30
        第三次;读取后面30个字节, 把数组给装满, 函数返回30
        第四次:读取剩余的10个字节, 会把数据放在数组当中, 函数返回10
        第五次:没有数据可以被读取了, 函数返回0*/
     char arr[4];
     int n;
    //文件中的数据: abc你好你好
    //第一次读取: abc+ 半个你
    //第二次读取:半个你 + 好+ 半个你
    //第三次读取:半个你 + 好
     while ((n = fread(arr, 1, 4, file)) != 0)
     {
         for (int i = 0; i < n; i++)
         {
             printf("%c", arr[i]);
         }
     }
    //3.关闭文件
    fclose(file);
	return 0;
}

(6)三种写出数据的方式

写出数据:把程序中的数据,写到本地文件中永久存储

书写步骤:①打开文件fopen

②写数据 fputc fputs fwrite

③关闭通道 fclose

#include<stdio.h>
int main()
{
	/*
打开文件: fopen
写出数据: fputc 一次写一个字符,返回写出的字符
fputs 一次写一个字符串,写出成功返回非数,一般忽略返回值
 fwrite 一次读多个,返回写出的个数
关闭文件: fclose
E:\a\aa.txt
*/
//1.打开文件
	FILE* file = fopen("E:\\a\\aa.txt", "w");
	//2.写出数据
	//fputc一次写一个字符, 返回写出的字符
	// a - - - 97
	int c = fputc(97, file);
	printf("%c\n", c);
	//fputs 一次写一个字符串,写出成功返回非数,一般忽略返回值
//细节: 因为如果写出失败,那么就会有一个EOF的错误
	int n = fputs("你好你好", file);
	printf("%d\n",n);
	//fwrite 一次读多个,返回写出的字节个数
    char arr[] = {97, 98 , 99 , 100 , 101}; 
	int n2 = fwrite( arr, 1, 5, file);
	printf("%d\n", n2);
    //3.关闭文件
    fclose(file);
	return 0;
}

 

(7) 多种读写模式

#include<stdio.h>
int main()
{
	/*
	模式:
	r:read只读模式,不能写,数据是无法写到本地文件的
	w:write只写模式
	细节1: 如果文件不存在, 那么会把文件创建出来, 但是要保证前面的文件夹都是在存在
细节2: 如果文件已经存在, 会把文件清空
细节3: 上面的创建或者清空, 都是 fopen这个函数底层干的事情
	a:append追加写出模式
	细节1:如果文件不存在,那么会把文件创建出来, 但是要保证前面的文件夹都是在存在
细节2:如果文件已经存在,不会清空,而是接着写 (续写模式)
细节3:上面的创建或者续写,都是 fopen这个函数底层干的事情
*/
//1.打开文件
	FILE* file = fopen("E:\\a\\aa.txt", "r");
	//2.写出数据
	//fputc一次写一个字符, 返回写出的字符
	// a - - - 97
	int c = fputc(97, file);
	printf("%d\n",c);
	//fputs 一次写一个字符串,写出成功返回非数,一般忽略返回值
	int n = fputs("你好你好", file);
	printf("%d\n",n);
	//fwrite 一次读多个,返回写出的字节个数
    char arr[] = {97, 98 , 99 , 100 , 101}; 
	int n2 = fwrite( arr, 1, 5, file);
	printf("%d\n", n2);
    //3.关闭文件
    fclose(file);
	return 0;
}

 (8)拷贝文件

#include<stdio.h>
int main()
{
	/*练习:
		利用代码拷贝文件
		要求:
		把桌面上的a.wmv拷贝到桌面的 aaa文件夹中
		纯文本文件 : r w a
		windows里面的记事本能打开并且能读懂的,就是纯文本文件
		txt md lrc
		二进制文件 : rb wb ab
		b : binary
		*/
	// 1.打开a.wmv文件(数据源)
	FILE * file1 = fopen("C:\\ Users\\1\\ Desktop\\a. wmv", "rb");
	// 2.打开 copy.wmv(目的地)
	FILE* file2 = fopen("C:\\ Users\\1\\ Desktop\\ aaa\\ copy. wmv", "wb");
	//3.利用循环读取数据源,读完之后,再写到目的地
	char arr[1024];
	int n;
	while ((n = fread(arr, 1, 1024, file1)) != 0)
	{
		//就要把读取到的数据,再写到目的地文件当中
		fwrite(arr, 1, n, file2);
	}
	//4.关闭文件
	fclose(file1);
	fclose(file2);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值