数据类型
该链接是引用他人的讲解,比较详细,请勿作为商用
链接:
1、基本数据类型
整型 int -2^31-1 ~ 2^31-1
短整型 short -2^15-1 ~ 2^15-1
长整型 long -2^63-1 ~ 2^63-1
长长整型 long long
无符号整型 unsigned int
字符型 char
单精度浮点型 float
双精度浮点型 double
长双精度浮点型 long double
概念:表达整数类型的数据
格式控制符
系统字长/数据类型所占字节大小
sizeof:不是函数,是一个特殊的运算符,计算数据所占字节大小
sizeof() 是一种内存容量度量函数,功能是返回一个变量或者类型的大小(以字节为单位);在 C 语言中,sizeof() 是一个判断数据类型或者表达式长度的运算符
sizeof有两种语法形式,如下:
sizeof(type_name);//sizeof(类型)
sizeof object;//sizeof对象即定义的变量或调用的函数
sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。sizeof对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算。
sizeof(2);//2的类型为int,所以等价于sizeof(int);
sizeof(2+3.14);//3.14的类型为double,2也会被提升成double类型,所以等价于sizeof(double);
sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用
C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值
数组的sizeof值等于数组所占用的内存字节数
指针变量的sizeof值与指针所指的对象没有任何关系,相同位操作系统中的所有的指针变量所占内存大小相等,64位指针是八字节,32位是4字节
strlen函数:计算的是字符串str的长度,从字符的首地址开始遍历,以 '\0' 为结束标志,然后将计算的长度返回,计算的长度并不包含'\0'。
字符在C里面是以ASCII码值存在的
97 ‘a’ 65 ‘A’ 48 ‘0’
字符串
布尔型数据
变量的赋值分为两种方式:
- 1.先声明再赋值
- 2.声明的同时赋值
常量/变量:不可改变的内存称为常量,可以改变的内存称为变量
在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。
符号常量在使用之前必须先定义,其一般形式为:#define 标识符 常量值
2.指针类型
指向内存地址
3.构造类型
数组 arr[ ]
结构体 struct
联合体 union
枚举 enum
4.void 空类型
数的表示方式(进制之间的转换)
十进制:平常见到的数字,如:100 200 300
二进制:通常由0和1组成,逢2进1,如:101 111 1101
八进制:通常由0到7组成,逢8进1,由0开头的,如:0123 0246 0756
十六进制:通常由0到F组成,逢16进1,由0x开头,如:0x123 0xfd 0xca25
十进制转二进制,八进制,十六进制
除N取余,逆序输出
二进制转十进制,八进制,十六进制
二进制转十进制
将每一位的位权展开,相乘相加
二进制转八进制
将二进制数每三位为一位八进制数,按位权展开,相乘相加,在写在一起即可
二进制转十六进制
将二进制数每四位为一位十六进制数,按位权展开,相乘相加,在写在一起即可
八进制转十进制、二进制、十六进制
八进制转十进制
将每一位的位权展开,相乘相加
八进制转二进制
每一位八进制数,作为一个三位二进制数,转换之后,写在一起
八进制转十六进制
八进制数转成二进制数,二进制数转成十六进制
八进制数转成十进制数,十进制数转成十六进制
十六进制转十进制、二进制、八进制
十六进制转十进制
将每一位的位权展开,相乘相加
十六进制转二进制
每一位十六进制数,作为一个四位二进制数,转换之后,写在一起
十六进制转八进制
十六进制数转成二进制数,二进制数转成八进制
十六进制数转成十进制数,十进制数转成八进制
符号位:
链接:https://blog.csdn.net/Romantic_wennuan/article/details/123724265
原码:
将一个整数转成二进制,这个二进制就表示原码
反码:
对于正数来讲,反码就是原码,对于负数,反码就是除了符号位之外全部取反
补码:
对于正数来讲,补码就是原码,对于负数,补码就是反码加1
溢出:
计算机里面做算术的加减法:
将要进行算术运算的数,先转成原码,原码转成补码,两个补码加起来之后,在转成原码(将两个加起来的补码减1,在除了符号位全部取反)
格式化输入输出
#include <stdio.h>
scanf跟printf这个两个函数,叫做变参函数
格式化输入scanf:
标准输入
int scanf(const char *format, ...);
format:格式控制串
1 scanf(); // 格式化输入函数
2 fgets(); // 字符串输入函数
3
4 int a;
5 float f;
6 scanf("%d", &a); // 从键盘输入一个整型,放入指定的内存地址 &a 中
7 scanf("%f", &f); // 从键盘输入一个浮点数,放入指定的内存地址 &f 中
8
9 scanf("%d%f", &a, &f); // 从键盘依次输入一个整型和一个浮点型数据,用空白符隔开
10
11 char c;
12 char s[10];
13 scanf("%c", &c); // 从键盘输入一个字符,放入指定的内存地址 &f 中
14 scanf("%s", s); // 从键盘输入一个单词,放入指定的数组 s 中(注意不是&s)
15
16 fgets(s, 10, stdin); // 从键盘输入一行字符串,放入数组 s 中
注意1:
格式控制是怎样的格式,那么在输入的时候,就要按照这个格式来
如果在格式控制字符串中含有除格式声明以外的字符,要在输入时在相应的位置输入相同的字符
scanf()会进行一个“吃空白”的操作,也就是把空格、回车、制表符全都吃掉
scanf()默认把空白符作为分割多个输入的标志,所以当你输入空白的时候,scanf()会认为你要进行下一个输入,从而停下来等你输入下一个非空白数据
但是当参数字符串的处理结果已经来到结尾或者输入流来到末尾的时候,scanf()不会认为你将继续进行输入,也就不会进行吃空白行为
当参数为"%d"的时候,此时没有处理到结尾,scanf()会认为你要继续进行输入。然而你输入了空格或者回车,scanf()认为他们是分隔符,于是“吃空白"吃掉了。换句话来说,scanf()依旧在等待你的输入,只有当你输入不是空白的字符以后,scanf()才会用它与参数字符串里面的空格比较。
#include<stdio.h>
int main()
{
int a,b,arr[5];//定义
int i=0;//初始化
//scanf函数的定义:其接收元素必须是类型与控制字符串元素对应的变量的地址。
//&是取地址操作符,当接收元素不是地址时要用&获得变量的地址,当接收元素已经是地址时就不用&了
//数组名是数组第一权个元素的地址,所以数组名就可以不用加&进行运算
scanf("%d",&a);
scanf("%d %d",&a,&b);
// scanf("%d ",&a);
//%d后有一个空白符,产生”吃空白”的现象
//如果在格式控制字符串中含有除格式声明以外的字符,
//要在输入时在相应的位置输入相同的字符
for(i=0;i<5;i++)//for循环,因为数组是多个元素的集合,只能使用循环从第一个逐个遍历赋值。
//数组只能够整体初始化,不能被整体输入赋值。
//只能使用循环从第一个逐个遍历赋值。
{
scanf("%d",&arr[i]);//数组前面的&可加,可不加
}
// scanf("%d ",&a);
//%d后有一个空白符,产生”吃空白”的现象
//如果在格式控制字符串中含有除格式声明以外的字符,
//要在输入时在相应的位置输入相同的字符
// 此处输入时必须带逗号
scanf("%d,%d", &a, &b);
// 此处必须先输入a=,然后才能输入整数
scanf("a=%d", &a);
// 此处结束输入时按下的回车符将被scanf()误以为格式控制符,无法正常结束输入
scanf("%d\n", &a);
return 0;
}
注意2:
int main()
{
// scanf() 试图从键盘读取两个整数
// 返回值 n 代表成功读取的个数,比如:
// 输入100 200,则 n 将等于2
// 输入100 abc,则 n 将等于1
// 输入abc xyz,则 n 将等于0;输入abc 200,n也将等于0
int n = scanf("%d%d", &a, &b);
// 根据 scanf() 的返回值,判断用户是否输入了正确的格式
while(n != 2)
{
// 需要清空缓冲区并提示用户重新输入
char s[50];
fgets(s, 50, stdin);
printf("请重新输入两个整数\n");
n = scanf("%d%d", &a, &b);
}
return 0;
}
格式化输出printf:
int printf(const char *format, ...);
format:格式控制串
为什么要刷新输出缓冲区?
数据输出,要通过输出缓冲区刷新到输出设备上面,
如何刷新输出缓冲区
1、’\n’
2、程序退出
3、后面scanf()
4、当输出缓冲区满
5、fflush(文件指针)l
类型转换:
隐式转换示例代码
编译过程
编译器:gcc
gcc test.c -o test
将一个.c源程序编译得到可执行文件,中间需要经过预处理、编译、汇编、链接四个过程
预处理
解析预处理命令,比如#include #define #if,会将宏做替换,展开。预处理会注释掉代码里面写的注释,预处理过程不会检查语法
gcc test.c -o test.i -E
编译
将预处理之后得到的.i文本文件,编译成某个平台的汇编代码,这个过程会检查语法
gcc test.i -o test.s -S
汇编
将编译的汇编文件编译成可重定向文件,这个文件现在不能运行
gcc test.s -o test.o -c
链接
连接一些函数库,找到函数的地址
gcc test.o -o test
四个过程,可以全部一起进行,直接得到目标文件
gcc test.c -o test
标识符
1、C语言规定,标识符可以是字母(A~Z,a~z)、数字(0~9)、
下划线_组成的字符串,不能有其他字符。
2、以字母和下划线打头,不能以数字开头
3、不能是C语言自带的关键字
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello world!\n");//\n是转义字符中的换行符
return 0;
}
在使用标识符时还有注意以下几点:
- 标识符的长度最好不要超过8位,
- 因为在某些版本的C中规定标识符前8位有效,
- 当两个标识符前8位相同时,则被认为是同一个标识符。
- 标识符是严格区分大小写的。例如Imooc和imooc 是两个不同的标识符。
- 标识符最好选择有意义的英文单词组成做到"见名知意",不要使用中文。
- 标识符不能是C语言的关键字。
转义字符
\a 响铃(BEL)
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表
\v 垂直制表
\\ 表示一个反斜线\
\' 表示一个单引号
\" 表示一个双引号
\? 表示一个问号
\0 空字符
\ddd 1-3位八进制所代表的任意字符
\xhh 十六进制所代表的任意字符
运算符
算术运算符
由算术运算符组成的表达式,称为算术表达式
关注点:
1.1、-号在做取负运算的时候,相当于对这个操作数取绝对值
1.2、/号在做算术运算的时候,除数不能为0
1.3、%取模运算在做算术运算的时候,右边的除数不能为0,左右两边的操作数都不能为浮点型数据
1.4、自增和自减运算,分为前缀和后缀两种,以运算符来看,前缀的自增/自减,先自增/自减,在参数运算,以运算符来看,后缀的自增/自减,先参与运算,在自增/自减
++a以运算符来看,叫前缀,如果以a来看,叫后缀
a++以运算符来看,叫后缀,如果以a来看,叫前缀
运算符里面有单目运算符,双目运算符,三目运算符,目指的是运算符两边跟的参数
经典的笔记题:
int n = 20;
printf(“%d %d %d\n”, ++n, n++, ++n);
请问:输出结果是什么
//23 21 23
在Linux里面,printf语句从右往左运算,在printf语句里面一个变量进行多次自增自减,前缀的自增/自减,会拿最后的运算结果做输出,而后缀的自增/自减,会直接做输出
关系运算符
关注点:
2.1、==等于,是两个等号,不是一个等号,一个等号叫做赋值
一般在判断一个变量等不等于一个数的时候,通常将数写在变量的前面
由关系运算符组成的表达式,称为关系表达式
逻辑运算符
关注点:
3.1、逻辑与左右两边分别都有一个表达式,或者常量,变量,两边都为真,结果才为真
3.2、逻辑或左右两边分别都有一个表达式,或者常量,变量,左右两边,任意一边为真,结果就为真
3.3、逻辑非右边有一个表达式,或者常量,变量,真变假,假变真
特殊规则:
3.1、逻辑与运算,如果左边一开始就为假,右边的表达式就不在计算了,如果左边一开始就为真,右边是需要计算的
3.2、逻辑或运算,如果左边一开始就为真,右边的表达式就不在计算了,如果左边一开始是为假,右边是需要计算的
在C里面,非0为真,0为假
由逻辑运算符组成的表达式,称为逻辑表达式
做逻辑运算的时候,跟算术运算,关系运算一样,都是需要去考虑到优先级的问题,优先级高的先做,优先级低的后做,比如:加减乘除同时都有的时候,乘除法比加减法先做,如果优先级是一样的,那么就会按照结合性来,左结合从左往右计算,右结合从右往左算,例如:赋值运算符=,a=3;右结合 a+c == d;左结合
#include<stdio.h>
int main()
{
int a=20;
int b=30;
//int c=(a-20)||b-10;//判断结果为真,输出1
//a = 20 b = 30 c =1
//int c=(a-20)||(b-=20);// b-=20 b = b-20
//a:20 b:10 c:1
//int c=(a-20)&&(b-=20);
a:20 b:30 c:0
//int c = (a-10) || b-=20;
//a:20 b:30 c:1
int c;
//if(a++&& (c=b+20))
//a:21 b:30 c:50
//if(a++|| (c=b+20))
//a:21 b:30 c:随机值
//if((a-=20)&& (c=b-20));
//a:20 b:30 c: 随机值
if(a-=20&& (c=b=b-20));
//因为优先级,所以类似于( a -= 20&&(a=b=b-20) )
//因为20非零所以 20&&(a=b=b-20)为真 等于1
//a -=1 a=20-1=19; c:10
//if(a-=20||(c=b=b-20));
//a:19 b:30 c:随机值
printf("%d %d %d",a,b,c);
return 0;
}
位运算符
关注点:
4.1、按位取反:
单目运算符,将操作数的每一位进行取反
4.2、按位异或:
双目运算符,将两个操作的每一位进行异或运算
相同为0,不同为1
4.3、按位与:
双目运算符,将两个操作数的每一位进行位与运算
4.4、按位或:
双目运算符,将两个操作数的每一位进行位或运算
4.5、左移运算:
双目运算符,将操作数的每一位往左移,空出来的位置补0
4.6、右移运算:
双目运算符,将操作数的每一位往右移,移出去的位不要了,左边空出来的位置,有符号位,将符号位补过来,没有符号位,就不管
练习:
#include<stdio.h>
int main()
{
int a=0xfedc98a;
//int b=a^(1<<15);
int b = (~(a<<15))&a;
printf("%x\n%x\n", a, b);
printf("%x",b);
return 0;
}
// 有一个无符号32位整数,0x3fedc98a,写一个表达式去做如下操作
// (1)判断这个数的第15位是0还是1
// (2)将这个数的第16位取反,其他位不变
// (3)将这个数的第20位设置为0,其他位不变
// (4)将这个数的第25位设置为1,其他位不变
// (5)将这个数的第12位和第13位设置为0,其他位不变
#include <stdio.h>
int main(int argc, char const *argv[])
{
unsigned int a = 0x3fedc98a;
// (1)判断这个数的第15位是0还是1
//11111111 11111111 11111111 11111111
//00000000 00000000 10000000 00000000
if (1<<15 & a)
{
printf("第15位是1\n");
}
else
{
printf("第15位是0\n");
}
// (2)将这个数的第16位取反,其他位不变
//11111111 11111111 11111111 11111111
//00000000 00000001 00000000 00000000
a = a^(1<<16);
// (3)将这个数的第20位设置为0,其他位不变
//11111101 11111111 11111111 11111111
//11111111 11101111 11111111 11111111
a = a & (~(1<<20));
// (4)将这个数的第25位设置为1,其他位不变
//11111101 11111111 11111111 11111111
//00000010 00000000 00000000 00000000
a = a | (1<<25);
// (5)将这个数的第12位和第13位设置为0,其他位不变
a = a & (~(0x3<<12));//0x3 0011将其移动12位
printf("a = 0x%x\n", a);
//0x3fecc98a
return 0;
}
变量的赋值分为两种方式:
- 1.先声明再赋值
- 2.声明的同时赋值
常量
在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。
符号常量在使用之前必须先定义,其一般形式为:#define 标识符 常量值
#include <stdio.h>
#define POCKETMONEY 10 //定义常量及常量值
int main()
{
// POCKETMONEY = 12; //小明私自增加零花钱对吗?
printf("小明今天又得到%d元零花钱\n", POCKETMONEY);
return 0;
}
三目运算符
三目运算符:?:,其格式为:
表达式1 ? 表达式2 : 表达式3;
执行过程是:
先判断表达式1的值是否为真,如果是真的话执行表达式2;如果是假的话执行表达式3。
判断费用
#include <stdio.h>
int main()
{
//定义小编兜里的钱
double money =12.0 ;
//定义打车回家的费用
double cost =11.5 ;
printf("小编能不能打车回家呢:");
//输出y小编就打车回家了,输出n小编就不能打车回家
printf("%c\n",money>=cost?'y':'n');
return 0;
}
控制流
顺序结构:
从头到尾,中间不经过任意一条岔路,顺序结构
分支结构
单分支:if分支
#include<stdio.h>
int main()
{
int num=4;
if(num=5)// = 赋值,== 判断相等
printf("haha\n");//会打印haha
return 0;
}
将三个数从大到小输出
#include <stdio.h>
#include <stdlib.h>
//将三个数从大到小输出
int main()
{
int a=0;
int b=0;
int c=0;
scanf("%d%d%d",&a,&b,&c);//输入数字时,每个数字要用空格分开
//算法实现
if(a<b)
{
int temp=a;
a=b;
b=temp;
}
if(a<c)
{
int temp=a;
a=c;
c=temp;
}
if(b<c)
{
int temp=b;
b=c;
c=temp;
}
printf("%d%d%d\n",a,b,c);
printf("Hello world!\n");
return 0;
}
多分支:if...else
悬空else
#include<stdio.h>
int main()
{
int a=0;
int b=2;
if(a==1)
if(b==2)
printf("hehe\n");
else//else与最近的未匹配的if匹配
printf("haha\n");
return 0;
}
多分支:if...else if...else
#include <stdio.h>
#include <stdlib.h>
int main()
{
int age=100;
if(age<18)//如果条件成立,要执行多条语句,需要使用代码块,{}就是一个代码块
{
printf("未成年\n");
printf("不能谈恋爱\n");
}
else
{
if(age>=18&&age<28)
printf("青年\n");
else if(age>=28&&age<50)
printf("壮年\n");
else
printf("老年\n");
}
// if(age<18)
// printf("未成年\n");
// else if(age>=18&&age<28)
// printf("青年\n");
//else if(age>=28&&age<50)
// printf("壮年\n");
// else
// printf("老年\n");
//if(age<18)
// printf("未成年\n");
//else
//printf("成年\n")
//if(age<18)
//printf("未成年\n");
return 0;
}
关注点:
1、if语句后面是可以加一个分号的,代码不会出错,但是,没有任何效果,原因,if后面加分号,表示跟的是一条空语句
2、if…else..语句后面是不能加分号的,这样的话,你的else就没有if来进行配对使用
3、if语句可以单独使用,else语句不可以单独使用,else语句必须要跟if语句配套使用
4、不管是if语句还是else语句,代码块都必须用大括号{ }给他括起来,否则只有首句有效
5.if 后的else if可以有多条,但是else只能有一个,else表示最后一个结束
多分支:switch分支
逻辑:根据不同的条件执行不同的代码片段
语法:
switch(整型表达式)
{
case 整型常量: 表达式
…
}
C语言也可以称之为弱类型语言,默认隐式转换,通过地址更改const的变量值
关注点:
1、switch(week),week必须是一个整型的表达式,switch判断的数据必须是整型
2、case语句后面只能跟整型常量,字符型常量也行,不能包含const型数据,不允许出现定义和声明
3、break语句的作用是跳出整个switch语句,没有break,程序会略过下面case往下执行
4、default语句,不是必须的,可以放在任意位置,一般放在末尾,这种是不需要break语句,如果放在前面的其他地方,需要加上break语句
5. case 语句后不能添加continue,除非外面嵌套一层循环
练习:
输入数字判断星期几
#include <stdio.h>
int main(int argc, char const *argv[])
{
int week;
scanf("%d", &week);
switch(week)//判断星期几
{
case 1: printf("today is Monday\n");break;
case 2: printf("today is Tuesday\n");break;
case 3: printf("today is Wednesday\n");break;
case 4: printf("today is Thursday\n");break;
case 5: printf("today is Friday\n");break;
case 6: printf("today is Saturday\n");break;
case 7: printf("today is Sunday\n");break;
default:printf("no week day\n");
}
return 0;
}
利用switc,从键盘输入一个日期,计算这个日期的这一天是这一年的第几天,比如:
2022 10 14 ,2022年第287的一天
#include <stdio.h>
int main(int argc, char const *argv[])
{
int year,month,day;
scanf("%d%d%d",&year,&month,&day);
if((year%4==0&&year%100!=0)||(year%400==0))
{
if(month<1||month>12)
{
printf("月份错误!");
}
if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)
{
if(day>0&&day<=31)
{
switch(month)
{
case 1:printf("是%d年的第%d天\n",year,day);break;
case 3:printf("是%d年的第%d天\n",year,day+31+29);break;
case 5:printf("是%d年的第%d天\n",year,day+31+29+31+30);break;
case 7:printf("是%d年的第%d天\n",year,day+31+29+31+30+31+30);break;
case 8:printf("是%d年的第%d天\n",year,day+31+29+31+30+31+30+31);break;
case 10:printf("是%d年的第%d天\n",year,day+31+29+31+30+31+30+31+31+30);break;
case 12:printf("是%d年的第%d天\n",year,day+31+29+31+30+31+30+31+31+30+31+30);break;
}
}
else{
printf("日期错误!");
}
}
if(month==4||month==6||month==9||month==11)
{
if(day>0&&day<=30)
{
switch(month)
{
case 4:printf("是%d年的第%d天\n",year,day+31+29+31);break;
case 6:printf("是%d年的第%d天\n",year,day+31+29+31+30+31);break;
case 9:printf("是%d年的第%d天\n",year,day+31+29+31+30+31+30+31+31);break;
case 11:printf("是%d年的第%d天\n",year,day+31+29+31+30+31+30+31+31+30+31);break;
}
}
else{
printf("日期错误!");
}
}
if(month==2)
{
if(day>0&&day<=29)
{
switch(month)
{
case 2:printf("是%d年的第%d天\n",year,day+31);break;
}
}
else{
printf("日期错误!");
}
}
}else
{
if(month<1||month>12)
{
printf("月份错误!");
}
if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)
{
if(day>0&&day<=31)
{
switch(month)
{
case 1:printf("是%d年的第%d天\n",year,day);break;
case 3:printf("是%d年的第%d天\n",year,day+31+28);break;
case 5:printf("是%d年的第%d天\n",year,day+31+28+31+30);break;
case 7:printf("是%d年的第%d天\n",year,day+31+28+31+30+31+30);break;
case 8:printf("是%d年的第%d天\n",year,day+31+28+31+30+31+30+31);break;
case 10:printf("是%d年的第%d天\n",year,day+31+28+31+30+31+30+31+31+30);break;
case 12:printf("是%d年的第%d天\n",year,day+31+28+31+30+31+30+31+31+30+31+30);break;
}
}
else{
printf("日期错误!");
}
}
if(month==4||month==6||month==9||month==11)
{
if(day>0&&day<=30)
{
switch(month)
{
case 4:printf("是%d年的第%d天\n",year,day+31+28+31);break;
case 6:printf("是%d年的第%d天\n",year,day+31+28+31+30+31);break;
case 9:printf("是%d年的第%d天\n",year,day+31+28+31+30+31+30+31+31);break;
case 11:printf("是%d年的第%d天\n",year,day+31+28+31+30+31+30+31+31+30+31);break;
}
}
else{
printf("日期错误!");
}
}
if(month==2)
{
if(day>0&&day<=28)
{
switch(month)
{
case 2:printf("是%d年的第%d天\n",year,day+31);break;
}
}
else{
printf("日期错误!");
}
}
}
return 0;
}
输入年月日,判断是那年的第几天
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a, b, c, num;
scanf("%d %d %d",&a,&b,&c);
num = c ;//最后一个月的天数
for(int i = 1;i<=b-1;i++){
// printf("%d\n",i );
switch(i){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
{
num +=31;
// printf("%d\n",num);
break;
}
case 2:{
num +=((0 == a % 4 && a % 100 != 0) || (0 == a % 400))?29:28;
// printf("%d\n",(a%4)==0?29:28 );
break;
}
case 4:
case 6:
case 9:
case 11:{
num +=30;
break;
}
default:printf("error : month > 12\n");
}
}
printf("%d年%d月%d日是%d年的第 %d 天\n",a,b,c,a,num );
return 0;
}
循环
循环之间跟分支,都是可以嵌套使用的
while循环
语法:
while(条件判断)
{
代码块;
}
关注点:
1、条件判断,如果条件成立,则执行代码块,如果条件不成立,则退出循环
2、如果一开始条件就不成立,代码块一次都不执行
3、循环控制变量的初始化,写在while的前面,循环控制变量的更改,写在代码块里面
4、while循环的后面,可以写分号,写了之后,如果条件一开始成立,则会变成死循环,如果一开始条件不成立,则判断一次条件之后就直接退出
求最大公约数
include<stdio.h>
int main()
{
int a=0;
int b=0;
int c=0;
scanf("%d%d",&a,&b);
while(a%b)
//a%b为0则跳出循环,a%b不为0则继续循环
//辗转相除法
{
c=a%b;//可以省略,将c=移到while中
a=b;
b=c;
}
printf("%d",b);
return 0;
}
do…while循环
语法:
do
{
代码块;
}while(条件判断); //注意:do…while循环的while后面必须要跟分号
关注点:
1、与while,for循环不同的是,do…while循环是先执行一次代码块,在去判断条件是否成立,成立则继续往下执行,不成立则退出。
2、三个循环在条件不成立的情况下,do…while循环永远都会比while和for要多执行一次,但是如果条件成立,则不一定
3、do…while循环的while后面必须要跟上分号,但是while和for是可以跟,也可以不跟
for循环
语法:
for(表达式1;表达式2;表达式3)
{
代码块;
}
关注点:
1、表达式1的作用是对循环控制变量的初始化
2、表达式2的作用是判断循环条件是否成立
3、表达式3的作用是循环控制变量的更改
4、for循环执行的顺序,先表达式1,在表达式2,需要判断条件是否成立,如果成立,执行代码块,执行表达式3更改循环控制变量,接着在执行表达式2,判断条件是否成立,如果成立则继续,不成立则退出for循环
5、表达式1可以省略,循环控制变量初始化放在for循环的前面,表达式2可以省略,省略之后就变成死循环,表达式3可以省略,循环控制变量的更改就放在代码块,唯独里面的分号不能省略
6、for循环的后面,可以写分号,表示跟的是一条空语句,有可能会造成死循环
7、如果一开始条件就不成立,代码块一次都不执行
练习:
计算质数
// i = 2;
// int a = i++;//参与运算的自增/自减,只要变量是在自增/自减前面,先拿i的值参与运算,在做自增
// i = 2;
// int a = ++i;//参与运算的自增/自减,只要变量是在自增/自减后面,先做自增,在拿i的值参与运算
// i = 20
// printf("%d %d %d\n", ++i, i++, ++i);//23 21 23
#include <stdio.h>
int main(int argc, char const *argv[])
{
int num;
scanf("%d", &num);
if (num == 1)
{
printf("输入大于1的整数\n");
return 0;
}
if (num == 2)
{
printf("2是质数\n");
return 0; //跳出循环(这个循环指的是当前包含它的循环)
}
//表达式1,作用用来初始化条件变量
//表达式2,作用用来判断条件成立与否
//表达式3,作用用来更改条件变量
// 表达式1 表达式2 表达式3
for (int j = 2; j < num; ++j) //2 ~ num-1
{
int i = 2;
int flag = 1; //作用:用来判断这个数是不是质数,0不是,1是
while(i<j) //括号里面写循环的条件
{
if (j % i == 0)
{
flag = 0;
break;
}
i++;
}
if (flag == 1)
{
printf("%d是质数\n", j);
}
}
return 0;
}
计算3的倍数
#include<stdio.h>
int main()
{
int i=0;
for(i=0;i<100;i++)
{
if(i%3==0)
{
printf("%d\n",i);
}
}
printf("Hello world!\n");
return 0;
}
求闰年
#include<stdio.h>
int main()
{
int year =0;
int count=0;
for(year=1000;year<=2000;year++)
{
/*
if(year%4==0 && year%100!=0)//能被4整除且不能被100整除
{
printf("%d ",year);
count++;//计算闰年次数,每打一次闰年就加一
}
else if(year%400==0)//能被400整除
{
printf("%d ",year);
count++;
}
*/
if(((year%4==0) && (year%100!=0))||(year%400==0))
{
printf("%d ",year);
count++;
}
}
printf("\n count=%d\n",count);
return 0;
}
计算素数
#include<stdio.h>
int main()
{
int a=0;
int count=0;
for(a=100;a<200;a++)
{
int b=0;
/*
//试除法
for(b=2;b<a;b++)//先判断(b=(a-1))<a,再加1,最后一次循环就是b=a
{
if(a%b==0)
{
break;
//b每次循环前都是0,然后在第二个for循环中一直加到a
//每当取模为零时就跳出循环,判断a是否等于b
}
}
if(b==a)
{
count++;
printf("%d ",a);
}
*/
//优化方案:修改的是时间复杂度,不是代码
//sqrt()是开平方
// i=a*c =sqrt(a) *sqrt(a) a和c中至少有一个数字<=开平方a
for(b=2;b<sqrt(a);b++)//先判断(b=(sqrt(a)-1))<a,再加1,最后一次循环就是b>sqrt(a)
{
if(a%b==0)
{
break;
//b每次循环前都是0,然后在第二个for循环中一直加到a
//每当取模为零时就跳出循环,判断a是否等于b
}
}
if(b>sqrt(a))
{
count++;
printf("%d ",a);
}
}
printf("\ncount =%d ",count);
return 0;
}
计算9的出现次数
#include<stdio.h>
int main()
{
int a=0;
int count=0;
for(a=0;a<100;a++)
{
if(a%10==9)
{
count++;//计算个位
}
if(a/10==9)//计算机计算除法时只会得出商省略余数
//因为99中9出现了两次
//并列关系用同等的if
//如果是计算带9的数字个数则用else if
{
count++;//计算十位
}
}
printf("%d\n",count);
return 0;
}
分数求和
计算1/1-1/2+1/3-1/4.........-1/100
#include<stdio.h>
int main()
{
int a=0;
double sum=0.0;
int c=1;
for(a=1;a<=100;a++)
{
sum +=c*1.0/a;
c=-c;
}
printf("%1f\n",sum);
return 0;
}
计算亲密数
#include<stdio.h>
main()
{
int x, y;
for (int i = 2; i < 10000; i++)
{
x = 1;
for (int j = 2; j < i; j++)
{
if (i%j == 0)
x += j;
}
y = 1;
for (int k = 2; k < x; k++)
{
if (x%k == 0)
y += k;
}
if (y == i&&i>x)
printf("%d和%d互为亲密数\n",i,x);
}
system("pause");
}
打印三角形星星堆
#include <stdio.h>
int main()
{
int i, j, k;//i是行数,j是空格数,k是*数
for(i=1; i<8; i++)
{
// 观察每行的空格数量,补全循环条件
for(j=i; j<8; j++)
{
printf(" "); //输出空格
}
// 观察每行*号的数量,补全循环条件
for( k=0;k<2*i-1;k++)
{
printf("*"); //每行输出的*号
}
printf("\n"); //每次循环换行
}
return 0;
}
打印九九乘法表
#include <stdio.h>
int main()
{
// 定义相乘数字i,j以及结果result
int i, j, result;
for(i=1;i<=9;i++)//(i=9;i=>1;i--)两者刚好反向
{
for(j=1;j<=i;j++)
{
printf("%d*%d=%d ",i,j,result=i*j);
}
printf("\n");
}
return 0;
}
break
跳出当前循环/switch结构
1、break语句对if-else的条件语句不起作用。
2、在多层循环中,一个break语句只向外跳一层。
在 C语言 的 switch(开关语句)中,break 语句还可用来在执行完一个 case(分支)后立即跳出当前 switch 结构。
关注点:
break语句通常用在循环语句和开关语句中。当break用于开关语句switch中时,可使程序跳出switch而执行switch以后的语句;
如果没有break语句,则会从满足条件的地方(即与switch(表达式)括号中表达式匹配的case)开始执行,直到switch结构结束。
当break语句用于do-while、for、while循环语句中时,可使程序终止循环。
而执行循环后面的语句,通常break语句总是与if语句联在一起。即满足条件时便跳出循环。
break在while中的使用
#include <stdio.h>
int main ()
{
//局部变量定义
int a = 10;
// while 循环执行
while( a < 20 )
{
printf("a 的值: %d\n", a);
a++;
if( a > 15)
{
// 使用 break 语句终止循环
break;
}
}
return 0;
}
continue
continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为只是中止(跳过)本次循环,接着开始下一次循环。
关注点:
1.continue语句并没有使整个循环终止。
2.continue 只能在循环语句中使用,即只能在 for、while 和 do…while 语句中使用。
continue在if中的使用
#include<stdio.h>
int main()
{
int index = 10;
do{
index = index + 1;
if( index == 15 )
{ // index 等于 15 时跳过
continue;
}
printf( "index 的值为 %d",index);
}while (index < 20);
return 0;
}
continue 在while中的使用
#include<stdio.h>
int main()
{
int m=1;
int n=10;
int c=5;
while(m<n)
{
m++;
if(m==c)
{
continue;
}
printf("m的值%d\n",m);//位置不同,输出的结果不同
}
return 0;
}
continue 在do...while中的使用
#include<stdio.h>
int main()
{
int i = 1;
do
{
if (i == 5)
{
++i;
continue;
}
printf("%d ", i);
++i;
}while (i<=10);
printf("\n");
return 0;
}
continue 在for循环中的使用
#include <stdio.h>
int main()
{
int i = 1;
for (i = 1; i < 10; i++)
{
if (i == 5)
{
continue;
}
printf("%d ", i);
}
printf("\n");
return 0;
}
过滤除了0-9的数据
#include <stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)//输入字符串
//EOF:end of file,要输入非空白符,以ctrl Z结束,直到在屏幕输入ctrlZ,程序就会结束
{
if (ch < '0' || ch > '9')
{
continue;
}
putchar(ch);//输出字符串
}
return 0;
}
计算日期
#include <stdio.h>
int main()
{
// 定义需要计算的日期
int date = 0;
int year = 2022;
int month = 5;
int day = 4;
switch(month)
{
case 12:date+=30;
case 11:date+=31;
case 10:date+=30;
case 9:date+=31;
case 8:date+=31;
case 7:date+=30;
case 6:date+=31;
case 5:date+=30;
case 4:date+=31;
case 3:
if((year%4==0&&year%100!=0)||year%400==0)
{
date+=29;
}
else
{
date+=28;
}
case 2:date+=31;
case 1:date+=day;
printf("%d年%d月%d日是该年的第%d天",year,month,day,date);
break;
default:
printf("error");
break;
}
return 0;
}
goto语句
释义:无条件跳转
关注点:
1、标签,是由C标识符命名格式加上:组成的,比如:lable:
2、不建议大家使用goto语句,因为会破坏程序的阅读性
函数
函数先声明后定义
#include <stdio.h>
int plus(int x,int y); //此处有分号,表示函数的声明
int main()
{
int a,b,c;
a=1;
b=2;
c=plus(a,b); //函数的调用
printf("%d",c);
return 0;
}
int plus(int x,int y) //此处无分号,表示函数的定义
{
int result;
result=x+y;
return result;
}
函数直接定义
#include <stdio.h>
int plus(int x,int y) //此处无分号
{
int result;
result=x+y;
return result;
}
int main()
{
int a,b,c;
a=1;
b=2;
c=plus(a,b); //函数的调用
printf("%d",c);
return 0;
}
计算还剩多少桃子
猴子第一天摘下N个桃子,当时就吃了一半,还不过瘾,就又多吃了一个。
第二天又将剩下的桃子吃掉一半,又多吃了一个。以后每天都吃前一天剩下的一半零一个。
到第10天在想吃的时候就剩一个桃子了,问第一天共摘下来多少个桃子?并反向打印每天所剩桃子数。
#include <stdio.h>
int getPeachNumber(int n) //自定义函数
{
int num;
if(n==10)
{
return 1;
}
else
{
num = (getPeachNumber(n+1)+1)*2;//(n+1)是初值,再次+1是每次多吃了一个
printf("第%d天所剩桃子%d个\n", n, num);
}
return num;
}
int main()
{
int num = getPeachNumber(1);
printf("猴子第一天摘了:%d个桃子。\n", num);
return 0;
}
计算第五个人多少岁
有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。
问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2人大两岁。
问第2个人,说比第1个人大两岁。最后 问第1个人,他说是10岁。请问第5个人多大?
程序分析:
利用递归的方法,递归分为回推和递推两个阶段。
要想知道第5个人岁数,需知道第4人的岁数,
依次类推,推到第1人(10岁),再往回推。
#include <stdio.h>
int dfs(int n) //c中是自定义函数,而c++是自定义类
{
return n == 1 ? 10 : dfs(n - 1) + 2;//三目运算符(条件?执行1:执行2)
}
int main()
{
printf("第5个人的年龄是%d岁", dfs(5));
return 0;
}
extern全局变量
#include<stdio.h>
int main()
{
extern int x;//此处使用的是全局变量
printf("extern x=%d\n",x);
return 0;
}
int x=100;
计算车费
每公里单价计费2.3元
起步价13元(包含3公里)
晚上23点(含)至次日凌晨5点(不含)打车,每公里单价计费加收20%。
每次乘车加收1元钱的燃油附加税。
小明每天上下班都要打车,公司和家的距离为12公里,上午上班时间为9点,下午下班时间为6点。
请编写一个小程序计算小明每天打车的总费用。
#include <stdio.h>
float taxifee(int clock,int miles)
{
float money;
if(miles<=3)
{
money=14;
printf("费用为14\n");
}
else
{
if(clock>=23 || clock<5)
{
money=13+1+2.3*(miles-3)*1.2;
printf("夜间车费为:%f\n",money);
}
else
{
money=13+1+2.3*(miles-3);
printf("日间车费为:%f\n",money);
}
}
return money;
}
int main()
{
printf("打的总费用:%.1f\n",taxifee(9,12)+taxifee(18,12));
return 0;
}
数组
数组是多个相同类型元素组成的一个集合
(一次性定义多个相同类型的变量,存储到一片连续的内存中)空间是连续的
(1)数组的类型:数组元素的类型
(2)数组的元素:每个位置存放的内容
(3)数组的长度:数组的元素的个数
(4)数组占用的空间大小:数组的元素个数乘以每个元素所在的空间大小,实际占用的内存大小,数组定义的时候就确定好了
(5)数组的下标:放问元素的时候,第几个元素(从0开始,到长度-1结束)
一维数组
语法:
数据类型 数组名[数组长度];
例如:
int arr[5];
arr:数组的名字,访问元素的需要用到这个名字
[5]:数组的长度,有连续的5个相同类型的格子,每个格子存放元素
int:每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至是数组
初始化
定义数组的时候赋值,称为初始化
数组在做初始化的时候,最好就是直接定义的时候,确定好长度
int a1[5];
// int a2[];//错误的
//正常初始化
int a3[5] = {1,2,3,4,5};
//可以,定义的时候,只初始化一部分,没有初始化的呢,就默认初始化为0
int a4[5] = {1};
//可以,定义的时候,利用元素的个数,来确定数组的长度
int a5[] = {1,2,3};
//不可以,初始化的时候,越界了
// int a6[3] = {1,2,3,4,5};
int a = 4; //a叫做变量
int a7[a];//可以,C99标准之后,通过变量来确定数组的空间大小
// int a8[a] = {1,2,3};//不可以的,因为定义时做初始化,有可能会越界,因为长度是可变的
数组的元素访问,通过下标访问(0~长度-1)
比如:
int a[10]; //表示数组的长度
a[0];访问第一个元素 //数组里面的元素的下标
a[9];访问最后一个元素
数组名的含义
数组名相当于首元素的地址,大多数情况下都能适用
sizeof(数组名);
分两种情况:
一种定义了一个数组去求sizeof(数组名),整个数组的大小
一种数组以函数传参的方式,传进去,求sizeof(数组名),得到的是一个指针的大小
arr[n] ==> *(arr+n)
arr:表示首元素的地址
&arr[0]:表示首元素的地址
&arr:表示的是整个数组的地址
关注点:
1、如果定义一个数组,想要全部初始化为0,
int arr[5] = {0};
而不是
int arr[5];
这样定义数组是没有进行初始化的,不确定里面的元素值是多少
2、定义数组,想要一次性全部初始化完,
int arr[3] = {1,2,3};
而不是
int arr[3];
arr[3] = {1,2,3};//错误
原因:arr[3]表示数组的下标为3的这个元素,这样赋值不行的,而且arr[3]还是越界访问
数组使用
#include <stdio.h>
int main(int argc, char const *argv[])
{
// int arr[2] = {1,4};
//正常初始化
int arr1[2][3] = {{1,2,3}, {4,5,6}};
for (int i = 0; i < 2; ++i) //数组元素个数访问
{
for (int j = 0; j < 3; ++j)//元素里面这个数组的元素的方式
{
printf("%d\n", arr1[i][j]);//通常以数组元素的方式访问
// printf("%d\n", *(*(arr1+i)+j));//以地址做偏的方式访问
//这两种不建议使用
// printf("%d\n", *(arr1[i]+j));
// printf("%d\n", (*(arr1+i))[j]);
}
}
char arr3[20] = "world";
printf("%s\n", arr3);
char arr2[2][20] = {"nihao", "hello"};
for (int i = 0; i < 2; ++i)
{
printf("%s\n", arr2[i]);
}
//错误,初始化的时候越界,数组元素个数越界
// int arr2[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
//可以,只初始化一部分
// int arr3[2][3] = {{1},{4}};
//错误,初始化的时候越界,数组里面元素的数组
// int arr4[2][3] = {{1,2,3,4},{4}};
//错误,数组里面元素的数组的长度不确定
// int arr5[2][] = {{1,2,3},{4,5,6,7,8,9}};
//可以的,根据元素的个数来确定数组的长度
// int arr6[][3] = {{1,2,3},{4,5,6}};
//错误数组里面元素的数组的长度越界
// int arr7[][3] = {{1,2,3},{4,5,6,7,8,9}};
return 0;
}
计算字符出现的次数
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char a[128] = {0}; //0~126之间的字符 '\0'
int num[128] = {0};
printf("请输入一个字符串\n");
scanf("%s", a);
printf("a = %s\n", a);
int i, j, cnt = 0, k = 0;
int len = strlen(a); //strlen()计算字符串实际的字符个数
#if 0
for(i=0; i<len; i++)
{
for(j=i+1; j<len; j++)
{
if(a[i] == '\0') //如果还是空字符,那么就直接结束
break;
if(a[i] == a[j])
{
a[j] = '\0';
cnt++;
} //找到a[i]重复的次数,没有包含a[i]
}
if(cnt != 0)
{
a[i] = '\0';
num[k] = ++cnt;
k++;
cnt = 0;
}
if(a[i] != '\0' && cnt==0) //记录单个字符
{
a[i] = '\0';
num[k] = 1;
k++;
}
}
for(i=0; i<k; i++)
{
printf("%d", num[i]);
}
printf("\n");
#endif
for(i=0; i<len; i++)
{
num[a[i]]++;
}
for(i=0; i<128; i++)
{
if(num[i] != 0)
{
printf("字符%c出现%d次\n", i, num[i]);
}
}
printf("\n");
return 0;
}
数组偏移/指针
#include <stdio.h>
#define DISPALY_ADDR(_p, _addr_size) do \
{ \
printf("%p\t%hhu\n", _p, *_p++); \
}while(--_addr_size)
#define PRINT_VAR_ADDR(addr, _size) char *_p = (char *)addr; \
int _addr_size = _size; \
DISPALY_ADDR(_p, _addr_size)
int main(int argc, char *argv[])
{
int a[10] = {0xFFF, 0x2FF, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA};
#if 0
printf("a = %p, *a = %d\n", a, *a); //数组的名字就是数组首元素的地址
printf("a+1 = %p, *(a+1) = %d\n", a+1, *(a+1));
printf("a+2 = %p, *(a+1) = %d\n", a+2, *(a+2));
printf("----------------------------\n");
printf("&a[0] = %p\n", &a[0]);
printf("&a[0]+1 = %p\n", &a[0]+1);
printf("&a[0]+2 = %p\n", &a[0]+2);
printf("----------------------------\n");
printf("&a = %p\n", &a);
printf("&a+1 = %p\n", &a+1);
printf("&a+2 = %p\n", &a+2);
int *p = a;
printf("*(p+0) = %d, p[0] = %d\n", *(p+0), p[0]);
printf("*(p+1) = %d, p[1] = %d\n", *(p+1), p[1]);
printf("*(p+2) = %d, p[2] = %d\n", *(p+2), p[2]);
#endif
#if 0
int i;
for(i=0; i<10; i++)
{
// printf("%X\t", a[i]);
// printf("%X\t", *(a+i));
// printf("%X\t", *(i+a));
printf("%X\t", i[a]);
}
printf("\n");
#endif
//a == &a[0] 数组名等于数组首元素地址
// PRINT_VAR_ADDR(a, sizeof(a));
PRINT_VAR_ADDR(&a[0], sizeof(a));
return 0;
}
数组循环移位
#include <stdio.h>
void show_arr(int a[], int size)
{
int i;
for(i=0; i<size; i++)
printf("%d\t", a[i]);
printf("\n");
}
int main(int argc, char *argv[])
{
int i;
int n = 3; //初始化移动两位
int a[4] = {1, 2, 3, 4};
//移位之前的数组数据
show_arr(a, 4);
//进行右移n位
while(n-- > 0)
{
int tmp = a[3]; //把最后一个元素的值先存下来
for(i=3; i>0; i--)
{
a[i] = a[i-1]; //把前一个元素的值,给到当前元素
}
a[i] = tmp;
}
//移位之后的数组数据
show_arr(a, 4);
return 0;
}
数组的传参:
#include <stdio.h>
void func(int arr[])//以数组的形式接收
{
int sum = 0;
for (int i = 0; i < 5; ++i)
{
sum += arr[i];
}
printf("sum = %d\n", sum);
}
void func1(int *arr)//以指针的形式接收
{
int sum = 0;
for (int i = 0; i < 5; ++i)
{
sum += arr[i];
}
printf("sum = %d\n", sum);
}
//在C里面,不影响理解的情况下,指针做为地址来使用,指针就是一个地址
int main(int argc, char const *argv[])
{
int arr[10] = {1,2,3,4,5};
func(arr); //调用函数,实参给形参赋值,实参数组的首地址
func1(arr); //调用函数,实参给形参赋值,实参数组的首地址
return 0;
}
二维数组(多维数组)
概念:若数组的元素也是数组,则该数组称为多维数组
整型数组,就是每一个元素都是一个整数的数组
char型数组,就是每一个元素都是一个char型的数组
二维数组,就是每一个元素都是一维数组的数组
多维数组,就是每一个元素都是一个数组的数组
示例:
int arr[2][3];
代码释义:
arr[2]是数组的定义,表示有两个元素,int [3],一个有三个元素的整型数组
多维数组的语法跟普通的一维数组语法完全一致
定义一个二维数组
int arr[2][3];
对二维数组做初始化
访问数组里面的元素,也是通过下标来访问的
arr[0][1]
arr[0]:代表这是第一个元素
[1]:代表第一个元素里面这个数组的下标为1的元素
arr[0][1] ==> *(*(arr+0)+1)
数组元素的访问:
数组的万能拆解法:
任意的数组,不管有多复杂,其定义都由两部分组成
第一部分:说明数组名和元素的个数
第二部分:说明元素的类型,可以是任意的类型
示例:
int a[4]; 第一部分:a[4] 第二部分:int
int d[2][3]; 第一部分:d[2] 第二部分:int [3]
int e[2][3][4]; 第一部分:e[2] 第二部分:int [3][4] 第三部分:int [4]
int *b[4]; 第一部分:b[4] 第二部分:int *
函数指针数组
int (*c[4])(int, float); 第一部分:c[4] 第二部分:int (*)(int , float)
上述示例中,a[4],d[2],e[2],b[4],c[4]本质上并无区别,他们均是数组
唯一的不同,是它们所存放的元素的不同
二维数组的传参
#include <stdio.h>
void func(int arr[][3])//以数组的形式接收,通常用数组接收
{
int sum = 0;
for (int i = 0; i < 2; ++i)
{
for (int j = 0; j < 3; ++j)
{
sum+=arr[i][j];
}
}
printf("sum = %d\n", sum);
}
void func1(int (*arr)[3])//以指针的形式接收
{
int sum = 0;
for (int i = 0; i < 2; ++i)
{
for (int j = 0; j < 3; ++j)
{
sum+=arr[i][j];
}
}
printf("sum = %d\n", sum);
}
int main(int argc, char const *argv[])
{
int arr[2][3] = {{1,2,3}, {4,5,6}};
func(arr);
func1(arr);
return 0;
}
字符二维数组
#include <stdio.h>
void func(char arr[][20])//数组指针
{
for (int i = 0; i < 3; ++i)
{
printf("%s\n", arr[i]);
}
}
void func1(char (*arr)[20])//数组指针
{
for (int i = 0; i < 3; ++i)
{
printf("%s\n", arr[i]);
}
}
void func2(char **arr)//指针数组
{
for (int i = 0; i < 3; ++i)
{
printf("%s\n", arr[i]);
}
}
void func3(char *arr[])//指针数组
{
for (int i = 0; i < 3; ++i)
{
printf("%s\n", arr[i]);
}
}
int main(int argc, char const *argv[])
{
char names[][20] = {"zhangsan", "lisi", "wangwu"};
func(names);
func1(names);
char *s = "hello world";//true 字符指针常量
char s1[] = "zhaoliu";
char s2[] = "tianqi";
char s3[] = "wangba";
char *str1[] = {"zhaoliu", "tianqi", "wangba"};//可以
char *str2[] = {s1, s2, s3};//可以
func2(str1);
func3(str2);
return 0;
}
数组的遍历
#include <stdio.h>
int main(int argc, char const *argv[])
{
float score[4][3];
for (int i = 0; i < 4; ++i) //初始化4个学生的3科成绩
{
printf("请输入第%d位同学的三门成绩\n", i+1);
for (int j = 0; j < 3; ++j)
{
scanf("%f", &score[i][j]);
// scanf("%f", (*(score+i)+j));
}
}
float sum_score[4];
for (int i = 0; i < 4; ++i) //算总成绩
{
for (int j = 0; j < 3; ++j)
{
sum_score[i] += score[i][j];
// sum_score[i] += *(*(score+i)+j);
}
}
float avi_score[4];
for (int i = 0; i < 4; ++i) //算平均成绩
{
avi_score[i] = sum_score[i]/3;
}
int flag = 0; //下标为0的这个元素是最大
float num = sum_score[0];
for (int i = 1; i < 4; ++i)
{
if (num < sum_score[i])
{
num = sum_score[i];
flag = i;
}
}
for (int i = 0; i < 4; ++i)
{
printf("第%d位同学的总成绩:%.2f,平均成绩:%.2f\n",i+1, sum_score[i], avi_score[i]);
}
printf("第%d位同学成绩最高\n", flag+1);
return 0;
}
数组作为函数参数
整个数组当作函数参数,即把数组名称传入函数中
#include<stdio.h>
void temp(int arr[])
{
int i;
for(i=0;i<5;i++)
{
printf("%d\n",arr[i]);
}
}
int main()
{
int arr[5]={1,2,3,4,5};
temp(arr);
return 0;
}
数组中的元素当作函数参数:
把数组中的参数传入函数中
#include<stdio.h>
void temp(int arr)
{
printf("%d\n",arr);
}
int main()
{
int arr[5]={1,2,3,4,5};
temp(arr[3]);
return 0;
}
冒泡排序
#include <stdio.h>
int main()
{
double arr[10]={1.78, 1.77, 1.82, 1.79, 1.85, 1.75, 1.86, 1.77, 1.81, 1.80};
int i,j;
printf("\n************排队前*************\n");
for(i=0;i<10;i++)
{
if(i != 9)
printf("%1.2f, ", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
else
printf("%1.2f", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
}
for(i=8; i>=0; i--)
{
for(j=0;j<=i;j++)
{
if( arr[j]>arr[j+1]) //当前面的数比后面的数大时
{
double temp; //定义临时变量temp
temp=arr[j]; //将前面的数赋值给temp
arr[j]=arr[j+1]; //前后之数颠倒位置
arr[j+1]=temp; //将较大的数放在后面
}
}
}
printf("\n************排队后*************\n");
for(i=0;i<10;i++)
{
if(i != 9)
printf("%1.2f, ", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
else
printf("%1.2f", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
}
return 0;
}
数组查询
#include <stdio.h>
int getIndex(int arr[5],int value)
{
int i;
int index;
for(i=0;i<5;i++)
{
//请完善数组查询功能
if(arr[i]==value)
{
index=i;
break;
}
index=-1;
}
return index;
}
int main()
{
int arr[5]={3,12,9,8,6};
int value = 9;
int index = getIndex(arr,value); //这里应该传什么参数呢?
if(index!=-1)
{
printf("%d在数组中存在,下标为:%d\n",value,index);
}
else
{
printf("%d在数组中不存在。\n",value);
}
return 0;
}
多维数组的遍历
多维数组也是存在遍历的,和一维数组遍历一样,也是需要用到循环。不一样的就是多维数组需要采用嵌套循环
注意:多维数组的每一维下标均不能越界
#include <stdio.h>
#define N 10
//打印分数
void printScore(int score[])
{
int i;
printf("\n");
for(i=0;i<N;i++)
{
printf("%d ",score[i]);
}
printf("\n");
}
//计算考试总分
int getTotalScore(int score[])
{
int sum = 0;
int i;
for(i=0;i<N;i++)
{
sum+=score[i];
}
return sum;
}
//计算平均分
int getAvgScore(int score[])
{
return getTotalScore(score)/N;
}
//计算最高分
int getMax(int score[])
{
int max = -1;
int i;
for(i=0;i<N;i++)
{
if(score[i]>max)
{
max = score[i];
}
}
return max;
}
//计算最低分
int getMin(int score[])
{
int min =100;
int i;
for(i=0;i<N;i++)
{
if(score[i]< min)
{
min = score[i];
}
}
return min;
}
//分数降序排序
void sort(int score[])
{
int i,j;
for(i=N-2;i>=0;i--)
{
for(j=0;j<=i;j++)
{
if(score[j]<score[j+1])
{
int temp;
temp = score[j];
score[j] = score[j+1];
score[j+1]=temp;
}
}
}
printScore(score);
}
int main()
{
int score[N]={67,98,75,63,82,79,81,91,66,84};
int sum,avg,max,min;
sum = getTotalScore(score);
avg = getAvgScore(score);
max = getMax(score);
min = getMin(score);
printf("总分是:%d\n",sum);
printf("平均分是:%d\n",avg);
printf("最高分是:%d\n",max);
printf("最低分是:%d\n",min);
printf("----------成绩排名---------\n");
sort(score);
return 0;
}
指针
指针是C语言的灵魂
定义一个指针
语法:
指针类型 *指针名;
例如:
int *p;//既可以叫做指针变量,指针p,地址p
指针的尺寸
指针的尺寸大小由操作系统位数决定
32bit系统指针4Byte 64bit系统指针8Byte
指针的尺寸只跟系统的字长有关,跟指针的类型无关
指针变量
在C语言中,将专门用来存放地址的变量,称为指针变量,也可以叫做指针,指向内存单元
在不影响理解的情况下,一般对指针,指针变量,地址不区分
虽然不同的变量的尺寸不同,但是地址的表示方式却是一样的
虽然表示方式是一样的,但是他们代表的内存的尺寸和类型都不同,因此他们在逻辑上是严格区分的
内存地址:
系统为了便于区分每一个字节,对地址进行逐一编号,称为内存地址,简称叫做地址
基地址:
对于单个字节数据而言,基地址就是其所在内存的字节编号
对于多字节数据而言,基地址就是其所在内存的字节编码里面最小的那个
&a(取址符)
每个变量都有一块内存,&去取地址,地址(基地址)
为什么要用指针?
指针能够指向某块内存的地址,在使用的过程中,可以实际反映数据的,特别是在函数调用过程中。
什么时候用指针?
(1)如果我们将一个数据传入给函数,在函数体内只是访问这个数据,不想在函数外面修改数据本身的值,这个时候我们一般只用传普通变量
(2)如果我们将一个数据传入给函数,在函数体内数据发生变化,想要在函数外面也能体现出这个变化,那我们就传变量的地址,传指针(数据结构:链表,栈,队,二叉树)
解引用
使用指针,用*来解引用
int *p = malloc(sizeof(int));
*p = 10;//*p就表示解引用
数据交换
设计一个函数功能传入两个整数,实现两个整数的交换,在函数外面跟函数里面去打印交换的结果
#include <stdio.h>
void exchange(int *a,int*b)//指针调用,共用同一个地址
{
int *temp=0;
temp=a;
a=b;
b=temp;
printf("%s:a = %d b = %d\n", __FUNCTION__, a, b);
}
void func(int a, int b)//a:100 b:200
{
int tmp = a;
a = b;
b = tmp;
printf("%s:a = %d b = %d\n", __FUNCTION__, a, b);
//__FUNCTION__显示调用的函数名
}
void swap(int *a, int *b)
{
int tmp = *a;//*a,表示解引用
*a = *b;
*b = tmp;
printf("%s:a = %d b = %d\n", __FUNCTION__, *a, *b);
}
int main(int argc, char const *argv[])
{
int a = 100;
int b = 200;
printf("%p\n", &a);
printf("%p\n", &b);
func(a, b);
printf("%s:a = %d b = %d\n", __FUNCTION__, a, b);
exchange(a,b);
printf("%s:a = %d b = %d\n", __FUNCTION__, a, b);
swap(&a, &b);
printf("%s:a = %d b = %d\n", __FUNCTION__, a, b);
return 0;
}
如何了解一个函数
知道函数的头文件,知道函数的功能,看函数的参数列表,返回值
头文件 #include <stdlib.h>
参数列表:
size:申请的空间的大小(以byte计算)
返回值:void *
成功:一个合法的地址,一个合法的空间
失败:NULL
int *p;//野指针,没有合法的地址空间的指对于一个定义的指针,如果没有给它给具体的空间,地址,去访问属于非法访问,会出现下面的这个错误针
*p = 200;//不行,因为p没有具体的地址,没有具体空间位置,非法访问
对于一个匿名空间,用指针表示,用malloc函数申请空间
不同类型的指针
#include <stdio.h>
#include <stdlib.h>
struct Student
{
int num;
char name[20];
};
//函数名相当于是一个入口地址
void func(void)//一个没有返回值,没有参数列表的函数func
{
printf("func\n");
}
typedef unsigned int size_tt;
//用typedef这个关键字给复杂的类型取别名,而point就是这个函数指针的别名
typedef void (*point)(void);
int main(int argc, char const *argv[])
{
//int
int *p = malloc(sizeof(int));
//float
float *p1 = malloc(sizeof(float));
//char
char *p2 = malloc(sizeof(char));
//double
double *p3 = malloc(sizeof(double));
//struct Student //结构体指针变量
struct Student *s1 = malloc(sizeof(struct Student));
//指向数组的指针
int arr[3];
int *p4 = arr;
//指向函数的指针
void (*p5)(void) = func;
//void (*p5)(void) 就是一个函数指针,存放函数的入口地址,这种写法就比较复杂
p5();//与func();效果一样
point p6 = func;
p6();
return 0;
}
free:释放申请的空间,跟malloc成对使用
ptr:释放空间的地址
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// int *p;//野指针,没有合法的地址空间的指针
// *p = 200;//不行,因为p没有具体的地址,没有具体空间位置,非法访问
int arr[40];
int *p = malloc(sizeof(int));//在内存中的堆空间的位置申请的空间来使用
*p = 200;
int a = 20; //a是在栈空间里面的空间拿来使用的,由系统自动申请跟释放
int *p1 = &a; //p1常目标指针
free(p);
// int a = 10;
// int *p = &a; //定义一个指针变量,将a的地址赋值给p
// *p = 20;
// printf("&a = %p\n", &a);
// printf("p = %p\n", p);
// printf("a = %d\n", a);
// printf("*p = %d\n", *p);
return 0;
}
type类型:
给函数取别名
#include <stdio.h>
#include <stdlib.h>
struct Student
{
int num;
char name[20];
};
//函数名相当于是一个入口地址
void func(void)//一个没有返回值,没有参数列表的函数func
{
printf("func\n");
}
typedef unsigned int size_tt;
//用typedef这个关键字给复杂的类型取别名,而point就是这个函数指针的别名
typedef void (*point)(void);
int main(int argc, char const *argv[])
{
//int
int *p = malloc(sizeof(int));
//float
float *p1 = malloc(sizeof(float));
//char
char *p2 = malloc(sizeof(char));
//double
double *p3 = malloc(sizeof(double));
//struct Student //结构体指针变量
struct Student *s1 = malloc(sizeof(struct Student));
//指向数组的指针
int arr[3];
int *p4 = arr;
//指向函数的指针
void (*p5)(void) = func;
//void (*p5)(void) 就是一个函数指针,存放函数的入口地址,这种写法就比较复杂
p5();//与func();效果一样
point p6 = func;
p6();
return 0;
}