思维导图
day1 通过案例了解了单分支结构和多分支结构 顺序结构
c语言程序设计流程
1.分析建模 ---2.画流程图---3.翻译成C语言---4.编译成可执行程序---5.运行
1:输入一个正方形的边长,计算其周长和面积
2:输入一个长方形的长和宽,计算其周长和面积
3:输入一个圆的半径,计算其周长和面积
案例2:输入一个学生的年龄,输入一个学生C补习成绩
案例3:通过税前工资计算税后工资
day2 了解了基础库函数 定义变量 运算符 数据类型的转换 进制转换
1.定义变量 (在内存中分配空间)
类型名 变量名;
类型名 ----->基本数据类型
整数 int
小数 float
字符 char
变量名 ------>命名规范
(1).由数字,字母,下划线组成,首字母不能为数字
(2).不能为关键字或保留字
(3).尽量见名知意
2.C语言库函数
#include <stdio.h>
输入:scanf()
scanf(“格式化符号”,地址列表) 有个几个格式化符号,地址就多个
scanf("%md%mf",&r,&f); scanf可以控制宽度,但不能控制精度
输出:printf()
printf(字符串) printf(“helloworld\n”) 字符串会原样输出
printf(字符串+格式化符号(占位符),变量名列表) 前面有几个格式化符号,后边会多个变量名
%d ---->有符号整数
%f ----->浮点数
%c ----->字符
printf("半径为%md的周长为%-m.nf,面积为%f\n",r,len,area);
m:数据所占的宽度
n:数据所保留的精度
默认是右对齐,-表示左对齐
3.运算符:
1.算术运算符
+,-,*,/(整数/整数=整数),%(取模/求余),++(自增),--(自减)
+=,-=,*=,/=,%=
a+=1 < == > a=a+1
7/3=2 11/3=3 3.0/2=1.5 11%3=2 %(求余/取模)
a++ ++a
int x=5.5*3+5%4
8. 有整型变量x,单精度变量y=5.5,表达式x=(float)(y*3+((int)y)%4)执行后,x的值为( A)。
(A)17 (B)17.500000 (C)17.5 (D)16
4.数据类型的转换
隐式转换(自动转换 范围小---范围大)
范围小----->范围大的转换 int + float ---->float
int家族
short ---->2个字节的整数
int ----->4个字节的整数
long ------>4个字节的整数
short ----->signed short 有符号的整数
unsigned short ----->无符号整数
int+float----->float
short +int ---->int
signed+unsigned--->unsigned
float家族
float ---->4字节
double ---->8字节
long double
强制转换 (范围大--->范围小)
float f=13.5
(int)f
5.进制转换
day3 进一步了解顺序 选择结构
1.单分支选择结构
if (条件为真)
{
语句
}
2.双分支选择结构
if(条件为真)
{
语句1;
}else
{
语句2;
}
3.多分支选择结构
if(条件1为真)
{
语句1;
}else if(条件2为真)
{
语句2;
}else if(条件3为真)
{
语句3;
}……
else if(条件n为真)
{
}else
{
}
(1).当条件在某一个点上时,switch,case
(2).表达式的结果类型不能为浮点型
(3).标号必须为常量
(4).当表达式==标号时,执行标号后的语句,直到switch,case语句结束为止,或者碰到break语句提前结束
(5).case 和case之间,以及case和defalut之间没有顺序而言
switch(表达式)
{
case 标号1:
语句1
case 标号2:
语句2
case 标号3:
语句3
……
case 标号n:
语句n
default:
语句n+1
}
- 能被4整除但不能被100整除 year%4==0 && year%100!=0
||
- 能被400整除 year%400==0
- 什么是循环
- While语句
-
(1).循环的初始条件
while((2).条件判断) 条件判断,满足什么样的条件,循环继续执行
{
(3).循环体
(4).条件更新
}
- For语句
循环的初始条件若有多句话的逗号隔开
for(;;) ()中有且仅有两个分号
for((1).循环的初始条件;(2).条件判断;(4).条件更新)
{
(3).循环体
}
- Break跳出循环
-
Continue;结束本次循环,继续执行下一次循环
-
死循环
while(1)
{
}
for(;1;)
{
}
- 一维数组
假设我们要存储22121班40学生的C补习成绩float
存储的数据类型是一致的.
- 如何定义数组
元素的数据类型 数组名[元素的个数] 元素的个数必须为常量。
定义一个长度为5的float数组
float arr[5];
定义一个长度为10的int数组
int brr[10]
定义一个长度为20的char数组
char str[20];
- 数组初始化
1.部分初始化
部分初始化,未初始化的部分,如果是float,int会自动默认为0,char会自动默认为’\0’
float arr[5]={12,13,45}
int brr[10]={15,16,17}
char str[20]={‘h’,’e’,’l’,’l’,’o’}
我们可以使用该特点对数组进行初始化
float arr[5]={0}
int brr[5]={0}
char str[20]={‘\0’}
2.全部初始化
若进行全部初始化,可以将数组中的元素个数省略,个数由后面的具体赋值个数所决定
3.数组元素的访问
4.数组的其他操作
1.获得该数组元素的平均值
2.求数组中的最大值
3.求数组中的最小值
4.将数组中的元素按照从小到大的顺序排序
一.定义一个长度为5的float数组
- 输入
- 求平均值
- 求最大值
- 求最小值
- 进行排序(冒泡排序)
二.定义一个长度为20的char数组
char str[20]={‘\0’}
- 输入一个字符串 “hellobeijing”
- 获得字符串的长度,不包含’\0’
- 二维数组
1.冒泡排序
排序:就是比较大小,把一组数据按照从小到大或者从大到小的顺序进行排列
冒泡排序:一组数据在排序的过程中跟生活中冒泡很像,所以叫冒泡排序
冒泡排序的思想:一组数据,先将这组数据中最大的数找出来,然后放到这组数据的最右边,然后继续在剩下的数中找最大的数,并且挪到剩下的这些数中最右边的位置,依次类推。
如何找到最大的数并将这个数挪到最右边呢?
88 55 11 66 10
每一趟的目的都是为了找到无序序列中最大的数并挪到无序序列的最右边
每一趟都需要比较很多次,才能将最大的数挪到无序序列的最右边
每一次都是前一个数和后一个数两两比较,如果前面的后面的大,就交换位置,并且将大的数继续和下一个数再进行比较。
需要很多趟,而且每一趟都要很多次
第一趟: i 0
88 55 11 66 10
第一次比较: j 0 arr[0] > arr[1] 交换
55 88 11 66 10
第二次比较:j 1 arr[1] > arr[2] 交换
55 11 88 66 10
第三次比较:j 2
55 11 66 88 10 arr[2] > arr[3] 交换
第四次比较:j 3
55 11 66 10 88 arr[3] > arr[4] 交换
第二趟:i 1
55 11 66 10 88
第一次比较:j 0
11 55 66 10 arr[0] > arr[1] 交换
第二次比较:j 1
11 55 66 10 arr[1] < arr[2] 不交换
第三次比较:j 2
11 55 10 66 arr[2] > arr[3] 交换
11 55 10 66 88
第三趟:i 2
第一次比较:j 0
11 55 10 arr[0] < arr[1] 不交换
第二次比较:j 1
11 10 55 arr[1] > arr[2] 交换
11 10 55 66 88
第四趟:i 3
11 10
第一次比较:j 0
10 11 arr[0] > arr[1] 交换
最终结果:10 11 55 66 88
I 0 j 0-3
I 1 j 0-2
i 2 j 0-1
I 3 j 0
For(i = 0; i < 4; i++)
{
For(j = 0; j < 4-i; j++)
{
If(arr[j] > arr[j+1])
{
Int tmp = arr[j];
Arr[j] = arr[j+1];
Arr[j+1] = tmp;
}
}
}
2.字符串处理函数
是一些函数,这些函数的功能是处理字符串的,也就是操作的对象是字符串和字符数组
函数:一个独立的功能模块
函数名()
{
。。。
。。。 这些语句和在一起实现了一个独立的功能
}
字符串:字符串常量 “abcde” 常量区
字符数组:
全局:char str[10] = “abcde”; 静态区
局部:char str[10] = “abcde”; 栈区
Strlen:求字符串长度,遇到’\0’就结束,不算’\0’,比如:”abcd”长度是4
Strcat:拼接两个字符串的,char str1[10] = “abc”,char str2[10] = “edf”; “edf” ,str1:”abcedf”
Strcpy:拷贝字符串的,char str1[10] = “abc”,char str2[10] = “ed”; “ed” ,str1:”ed”
Strcmp:比较字符串大小的,”abc”和”abbc”,从左至右依次 比较字符的ASCII值,“abc” > “abbc”
使用scanf和printf需要加stdio这个头文件,使用字符串处理函数,需要加string.h这个头文件
- 字符串处理函数
是一些函数,这些函数的功能是处理字符串的,也就是操作的对象是字符串和字符数组
函数:一个独立的功能模块
函数名()
{
。。。
。。。 这些语句和在一起实现了一个独立的功能
}
字符串:字符串常量 “abcde” 常量区
字符数组:
全局:char str[10] = “abcde”; 静态区
局部:char str[10] = “abcde”; 栈区
Strlen:求字符串长度,遇到’\0’就结束,不算’\0’,比如:”abcd”长度是4
Strcat:拼接两个字符串的,char str1[10] = “abc”,char str2[10] = “edf”; “edf” ,str1:”abcedf”
Strcpy:拷贝字符串的,char str1[10] = “abc”,char str2[10] = “ed”; “ed” ,str1:”ed”
Strcmp:比较字符串大小的,”abc”和”abbc”,从左至右依次比较字符的ASCII值,“abc” > “abbc”
使用scanf和printf需要加stdio这个头文件,使用字符串处理函数,需要加string.h这个头文件
作业:
- 冒泡排序没有写完的继续,要求:你自己输入,比较,然后输出,注意编程规范
- 练习上课的strlen代码,自己在main中实现strlen函数的功能并测试
- 预习其他的字符串处理函数
Strcat:拼接两个字符串的,char str1[10] = “abc”,char str2[10] = “edf”; “edf” ,str1:”abcedf”
Str2的’e’把str1的’\0’覆盖掉,并且把str2的’\0’也拷贝过去了
Strcat(目的字符数组, 源字符数组/源字符串);
Gets(str1) 功能和scanf(“%s”, str1)一样,gets遇到回车就结束,scanf是遇到回车或者空格就结束
Puts(str1)功能和printf(“%s\n”, str1)一样,puts自动会输出’\n’,但是printf需要自己加’\n’才输出’\n’
Strcpy:拷贝字符串的,char str1[10] = “abc”,char str2[10] = “ed”; “ed” ,str1:”ed”
将str2的’e’’d’和’\0’都拷贝到str1中了
Strcpy(目的字符数组,源字符数组/源字符串);
Strcmp:比较字符串大小的,”abc”和”abbc”,从左至右依次比较字符的ASCII值,“abc” > “abbc”
Strcmp(字符数组1/字符串1, 字符数组2/字符串2);
比较规则:依次比较两个字符串的字符的ascii值,直到不相等或者是两个都是’\0’
返回值:
大于0 字符串1 > 字符串2
小于0 字符串1 < 字符串2
等于0 字符串1 == 字符串2
Strncat(目的字符数组, 源字符数组/源字符串, n);
功能:将源字符串的前n个字符连接到目标字符数据的后面,注意目标字符数组最后要有’\0’
Strncpy(目的字符数组,源字符数组/源字符串, n);
功能:将源字符串的前n个字符拷贝到目标字符数组,其余不变
Strncmp(字符数组1/字符串1, 字符数组2/字符串2,n);
功能:比较字符串1和字符串2的前n个字符
Char str[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’}; //error,没有结束符,‘e’后面是什么未知Char str[6] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
Char str[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’}; //error,str的大小是5 Char str[] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’,’\0’};
Char str[5] = “abcde”; //error,有’\0’,因为加上’\0’占了6个字节,但str只有5个字节Char str[6] = “abcde”;
Char str[] = “abcde”; //right,有’\0’,str占了6个字节
初始化后会自动加‘\0’ 但如果没有足够空间存储的话 就会出现一些问题 (Char str[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};)
- 二维数组
一维数组的定义:int arr[5]; 这是一个有5个int类型元素的一维数组
1 2 3 4 5
二维数组的定义:int arr[2][5]; 这是一个有2个int arr[5]类型元素的一维数组
1 2 3 4 5 一维数组
1 2 3 4 5 一维数组
二维数组的定义:数据类型 数组名[行数][列数];
行数:有几个一维数组
列数:一维数组的元素个数
所以:二维数组的本质就是数组元素类型为一维数组的数组
初始化:
Int arr[2][5] = {{1,2,3}, {4,5,6,7,8}};
Int arr[2][5] = {1,2,3,4,5,6];
字符二维数组:
Char str[3][10]; 这个数组可以存三个字符串,每个字符串长度最大为9
Char str[3][10] = {“abc”, “cdsdf”, “swfeqw”};
Str[0]就是”abc”的首地址(数组名)
Str[1]就是“cdsdf”的首地址
Char str[3][10] = {{‘a’,’b’,’c’,’v’,’h’,’h’,’\0’},{’t’,’y’,’o’,’\0’},{’r’,’t’,’\0’}};
- 指针
指针:本质就是地址
地址是什么?
数据和指令最终都要存放在内存,cpu按照我们写的程序去访问内存,内存本质就是一个一个字节组成的,也就是说我们写的程序要去访问内存的一个个字节进而访问指令或者数据,那要实现这个目的,每个字节都长的一样,如何区分?
给每个字节编号来区分
地址:内存的基本单元是字节,每一个字节都有自己的唯一的编号,通过这个编号就可以找到这个字节进而访问这个字节,这个编号就是这个字节的地址
Char 一个字节
Int 四个字节
Int arr[5] 20字节
不管是多少个字节,每一个字节都有编号,那最终访问到这么多字节,你必须知道从那个编号开始的(首地址)以及你能访问的字节的个数,你就可以访问到这些字节
指针:地址就是指针
指针常量:内存的编号就是指针常量,因为不会发生改变
指针变量:用来存放内存编号的一个变量,可以被修改
我们平时说的指针默认指的是指针变量,也就是说直接说指针,而不说指针变量
在指针里面 *的作用
- int *p;*的作用是标识符
- *p = a;*的作用是取值运算符,取了a空间里面的内容
- 分辨*的用法:标识符一般出现在定义的时候,会出现数据类型,取值运算符一般用在运算的时候,这时候不会出现数据类型。
Int * const p int const *p
*在const的左边还是右边
*在左边不能去修改p的指向,*在const的右边不能去修改值
左指,右数
空指针和野指针
野指针:没有明确的指向的指针
怎样避免野指针?
让野指针先指向零号地址,变成空指针(指向零号地址),零号地址不能被操作
指针和一维数组
数组名和首元素地址
指针就是地址,arr是一个地址,不过这个地址是计算机分配好的地址,不能人为的去进行改变,所以arr是指针常量。
定义指针指向数组
指针的运算
P+1:往地址增大的方向移动,移动一个数据类型 p没有发生改变
P-1:往地址减小的方向移动,移动一个数据类型 p没有发生改变
P++:往地址增大的方向移动,移动一个数据类型 p发生了改变
- -:往地址减小的方向移动,移动一个数据类型
P-q:自动的进行运算:(q-p) /sizeof(int) 相差多少个元素
*:取值运算符
&:取址运算符
当*和&出现在同一侧的时候,两个可以相互抵消
当*和&出现在等号的两边的时候,等号左边多一个*,右边多一个&
存储类型关键字
数据类型:int float double char short long struct union
控制类型:for if else while do break continue goto
存储类型:auto extern static register
其他: sizeof unsigned signed typedef
存储类型 数据类型 变量名
存储类型代表这个变量要存在内存的什么地方
Auto:定义的时候省略,一般修饰局部变量,存在内存的栈区。
Register:修饰的变量叫做寄存器变量,放在寄存器,放不下放到栈区
Extern:修饰外部变量 用来引用
Static:修饰的变量叫静态变量
Static修饰局部变量:延长了局部变量的生命周期
static修饰全局变量限制了全局变量的作用域
指针和字符串数组
Char Str[10];str[10] = “abcde”; 错的,不存在str[10]这个位置
Char Str[10];str = “abcde”; str是指针常量,常量不能赋值
Char *s;s = “abcde”; 正确,定义指针指向字符串,s+2输出cde
Char *s;*s = “abcde”;错误,再18.04里面char*s指向零号地址,零号地址不能被操作
Char str[10] = “abcde”;char *p = str; 正确 定义指针变量,将str(指针常量)赋值给变量
Char *s;s = “abcde”;s = “ab”; 正确,改变指针的指向
函数
函数是什么?
为了实现某些特定的功能的模块
char *strcat(char *dest, const char *src);
自定义函数
返回值类型 函数名(数据类型 参数1 ,数据类型类型 参数2。。。。。)
参数的个数是根据自己的需要去定义的,可以有一个,也可以没有,也可以有多个
形参和实参
形参和实参存放在栈区
函数的返回值
函数的返回值类型如果不写,默认的返回值类型是int
如果函数不写return ,函数的返回值类型默认还是int
函数的声明
地址传递
将x和y的地址传递给a和b,直接去操作x和y的地址,间接的去操作x和y的值。
二级指针
二级指针的定义
数据类型 ** 变量名
二级指针的数据类型和指向类型
Int a = 10;
Int *p = &a;
Int **q = &p;
二级指针的数据类型:int **
指向类型:int *
Const修饰二级指针
Int const **p:不能改变**p,也就是不能改变a的值,*p 和p可以改变
Int *const *p:
Int ** const q:不能改变q,也就是q永远指向p的地址,可以改变*q **q
Int a = 10; 0x100
Int *p = &a; 0x104
Int **pp = &p; 0x108
pp: 0x104
*pp: 0x100
**pp: 10
指针数组
定义
数据类型 * 数组名[元素个数]
里面的元素全部是指针类型
数组指针
数组指针和一维数组
数组指针的定义
数据类型 (*变量名)[一维数组的元素个数] = &数组名
数组指针输出元素(数组名有俩个意义1.数组首地址2.整个数组 )下面明显使用它的第二个含义
首先,我们应该知道的是数组名可以代表两种含义:①数组首元素地址②整个数组
显然在给数组指针赋值时,用到了②这个含义,取地址后代表了整个数组的地址,赋给了数组指针p,此处虽然地址值和光写一个a时所代表的值一样,但其意义却不一样(主要体现在他的类型上,代表的是一整个一维数组的地址,要赋给一个数组的指针)
值得注意的是,此处并没有对指针p的数组指向进行操作,而是直接对p进行了取值,表示的意思就是直接取这个数组,因为指针在访问连续内存空间的时候才显得有意义,如果是一维数组,p+1就会越界。所以我们一般不将数组指针用于一维数组,更多的是用在二维数组。
数组指针的大小和指向类型的大小
数组指针的运算
Int arr[3];
Int (*p)[3] = &arr;
p+1:地址增大的方向移动一个指向的数据类型(int [3])
p-1:地址减小的方向移动一个指向的数据类型(int [3])
p++:地址增大的方向移动一个指向的数据类型(int [3])
p--:地址减小的方向移动一个指向的数据类型(int [3])
P-q:无意义,给数组开辟空间是随机开辟的,并不能计算出两个数组之间差了多少个元素。
对于*(*(p+i)+j) ,一个通俗的理解,里层 * 是为了先选定数组,取数组,然后外层 * 再对数组中的元素取值,i,j分别是两次取值的偏移量。我们说对数组指针对二维数组才有意义,其意义主要体现在里层的p+i上,可以通过移动p,访问连续的不同一维数组。
数组指针永远指向一维数组
结构体
Struct people
{
Char name[20];
Int age;
Float height;
}
Int double float char
结构体是一个复合的数据类型,通常用在描述一个复杂的事物。
访问结构体的成员
- 定义结构体变量
- 访问
函数
- 返回值:用来就接收函数所要实现的功能是否成功的标志,如果成功,可以用返回值类型定义的变量来操作这个函数所实现的功能,比如fopen打开一个文件流,socket,创建一个套接字。
- 函数名:起自己想要实现的功能
- 形参:如果不知道先不写,先完成想要实现的功能,然后实现的功能里面缺什么类型的变量,就给形参定义成什么。比如 input(),实现数组的输入,里面的函数需要arr[i],所以形参需要写成 int * arr。
- 直接的操作形参从而实现间接的操作实参
指针
Int a;
Scanf(“%d”,&a);
Printf(“%d”,a);
Int a = 10;
Int *p = &a;
P也是一个变量,不过是一个指针类型的变量,&a是一个常量,不过是一个指针常量。
指针就是地址,地址就是指针,一个指针不能同时指向两个空间,两个指针可以同时指向一片空间
通过 int *p = &a;这里的*是标识符,表示p是一个int*类型的变量,也就是指针变量
p的数据类型是int *,指向类型是int,所以通过上式可以得到 p = &a;
通过p = &a,想要通过指针p取a的值,需要用到取值运算符:* *p = a;取p所指向的空间里面的内容。
Const修饰指针
Int const *p:const修饰的是*p(p所指向的空间里面的内容),这个内容变成只读,不能修改
Int * const p:const后面跟的是指针变量p,所以指针的指向变成只读,不能被更改
一级指针和一维整型数组
一级指针永远指向一个普通变量的地址p = &a;,给指针变量不断的+1,就会变成一片连续的空间,连续的空间一般是数组,起名叫arr,此时a就变成数组的第一个元素arr[0],在数组里面第一个元素的地址&arr[0]和数组名arr是一样的,所以int *p = arr;此时arr和p可以相互替换。Arr++不存在,因为常量不能自加。
一级指针和字符串数组
Char str[10] = “abcde”;
Char * p = str;此时用p输出str会输出abcde。
P = &str[2];此时输出cde,因为此时相当于重定向,字符串的起始位置变成c
Char str = “abcde”; //str字符型只能输入一个字符
Char str[10];str = abcde; // 不能这样定义 str为首地址不能
Char *s;*s = “abcde”; //野指针
Char *s;s = abcde;*s = b; abcde 放在常量区,常量区的内容不能被改变
二级指针
里面存放的是一级指针的地址
Int a = 10; 0x100
Int *p = &a; 0x104
Int **pp = &p; 0x108
Pp: 0x104
*pp: 0x100
**pp: 10
Int * const *pp:不能改变*pp,不能改变p的指向
Int ** const pp:不能改变pp,不能改变二级指针的指向
Int const **pp: 不能改**pp,也就是不能通过**pp去改变a的值
数组指针和指针数组