目录
第一章 基础语法
第一节 第一个C程序
主函数:
int main()
{
return 0;
}
即程序的入口,入口只能写一个
框架:
#include<stdio.h> 用别人的东西,包含一个叫stdio.h的文件
int main()
{
printf("hello World!\n");
return 0;
}
大括号内的我们称作代码,框架称作函数。
输入:scanf(“%d”,&变量名)
输出:printf会把其中的内容原封不动地输出,""里面的内容叫做字符串
int是整型的意思;return 0;是返回0的意思;main前面的int表示main函数调用返回整型值。
转义字符:\n表示需要在输出的结果后面换一行,\t表示横向跳格,‘\\’表示\。可以用printf(“\n”)换行
注释://作用是注释,把程序分部分标注,便于人的理解,他不会编译出来。
如“//初始化”;/**/用于多行或一行内的注释。
单一出口(printf)优选结构原则。
写程序是写步骤,不是说明!!
按tab缩进推进去
第二节 关键字
- 数据类型的关键字
char(%c,定义字符类型的变量用的:char ch=’a’。打印用%c),float(%f,单浮点型,带f的变量),double(%lf,双浮点型,精度更高),int(%d,定义整型变量),short(短整型short int a=11;)。
- 控制语句的关键字
if,else,break,for,while,do,switch,case,goto,default
标识符:代码中所有我们自己起的名字,比如函数名,变量名
只能由数字,字母,下划线组成,不能以数字开头,不能是关键字,比如 int if=5就不行
区分大小写
第三节 数据类型
基本类型:char,short int,int,long int,float,double
eg,1e-6表示10的-6次方
常量(整型%d,实型:float带小数点%f,字符型:单引号括起来%c,字符串:若干个字符的集合,双引号括起来%s)和变量(其值可以改变的量)
格式化输出:%s是字符串;%3d即要求宽度为3,不足3前面用空格补齐;%.2f(float)小数点后只保留两位。printf(“”)输出样式最终以字符串形式体现。
变量使用:先定义后赋值,数据类型 变量名=数据值;
unsigned只能和整型组合
第四节 运算符和表达式
在""内进行。
printf("12+34=%d",12+34);
%d含义是将其逗号后的数字填充为它。可以进行简单的数值运算。“”字符串被printf所支配,只能显示在大屏幕上!所以,要读取的%d目标数字要在,的幕后操作哦。
关于运算符
(1)算术运算符:加+ 减- 乘* 除/ 除余% 括号()
整数计算,结果是整数;整数和小数计算,结果一定是一个小数。取余时,运算的数据一定全是整数,余数的正负和第一个数字保持一致
隐式转换两条规则:取值范围小的,和取值范围大的计算,小的会自动提升为大的,再进行运算。short char类型的数据在进行运算时,先提升为int,再进行运算。范围从大到小:double>float>long long>long>int>short>char。short char类型进行运算时,先提升为int,再进行运算
强制转换:把取值范围大的,赋值给取值范围小的,就需要进行强制转换。格式:目标数据类型 变量名=(目标数据类型)被强转的数据;比如:int b=10;
short i=short(b)
单目运算最优先 a*+b a/-b ,对单独一个变量算子取正或者取负
齐次是乘除取余,加减,最后是赋值=
(2)自增,自减运算符:++,--
(3)赋值运算符,最常见的把右边赋予到左边,如a=6,还有+=,-=,*=,/=,%=
(4)关系运算符:==,!=,>,>=,<=,<,成立1(真),不成立0(假)。判断一个数是否为偶数,a%2==0;。
(5)逻辑运算符:!:非(取反);&&:与(而且),并且两边都成立,最终结果才成立,只要有一个不成立,最终结果都不成立;|| :或(或者),并且两边都不成立,最终结果才不成立,只要有一个成立,最终结果就成立
注意!!!A=b=6,对计算机的含义是a=(b=6)赋值了其右边哦。
(6)三元运算符:关系表达式?表达式1:表达式2。比如,求两个数的最大值用a>b?a:b。成立,a就是运算结果,不成立b就是运算结果
(7)运算符的优先级:小括号优于所有,一元>二元>三元,&&>||>赋值
浮点运算
在C中,两个整数的运算结果只能是整数,它会把除法的余数粗暴的丢弃掉。
10与10.0在c中是截然不同的数字,10.0是浮点数。
所以,计算机运算需要小数的时候,数值要改到浮点数,整数和浮点数一起,计算机会把整数改为浮点数,进行浮点运算,同样,printf去抓取显示的结果是浮点数,也要用%f去抓取。
int a=0;
int b=0;
printf("请输入几尺几寸,格式a,b");
scanf("%d,%d",&a,&b);
printf("你的身高是%f\n",(a+b/12.0)*0.3048);
浮点数的含义是小数点位置的是变化的数,类似的定点数是小数点后数字量固定的数,这些是计算机表示小数和无理数的方式。
方法二,是把变量类型从int改为double类型的浮点数变量,这样去进行浮点数运算。
double a=0;
double b=0;
printf("请输入几尺几寸,格式a,b");
scanf("%lf,%lf",&a,&b);
printf("你的身高是%f\n",(a+b/12)*0.3048);
转化为浮点运算时,要么把涉及除法的部分的数字带上小数点,要么把式子内的字母全部设为double!
变量形式,输入输出捆绑一致地变化。double c=(a+b)/2;比如这个,变量c是double浮点量,那么等号左边也应该搞浮点运算,要么a,b都double,要么2.0
在运用计算机进行算数问题时,要明白
我要什么样的变量来保存读进来的东西,
和什么式子去进行运算。
而在运算之前,我们可以处理变量!
用“进制”,即变量abc之间的关系,去减少变量或者化简运算。
进制里面用取余%
举个例子:
printf("请输入几小时几分,中间隔开");
scanf("%d %d",&hour1,&minute1) ;
printf("请输入几小时几分,中间隔开");
scanf("%d %d",&hour2,&minute2) ;
int a=hour1*60+minute1;
int b=hour2*60+minute2;
int c=(a-b)/60;
int d=(a-b)%60;
printf("时间的差值为%d小时%d分钟",c,d);
红色部分,找到了变量hour与minute之间的进制关系,化简了后期变量处理的数量。
/60划归到小时部分,并且运用了计算机整数运算里自动抹去余数的操作。
%60代表着取余数,得到分数的部分。
复合赋值
先把赋值右边的式子算完,再对赋值左边进行结合。
total+=(sum+100)/2; total*=sum+12;
Total=total+(sum+100)/2; total=total*(sum+12)
第五节 变量函数
首先,是要设置变量abc。
定义变量:<类型名称><变量名称>=初始值;
int是变量,const int 是常量,这是“变量”的类型。
例如,int price int amount int amount=100-price
int price=0; 这一行定义了一个变量。变量的名字是price,类型是int,初始值是0.
=本质是赋值!把右边的赋值给左边,赋予一个初始值。
有了初始值定基底,接下来要按照算法给变量我想要的值,赋值。
用printf(“请输入数值,格式a,b,c,”);
在大屏幕上摆出你的要求,最好提示一下格式,不然输入错了后面scanf没办法读取。输入格式要与scanf“”内的格式一致!“”外的间隔就是,不变。
我们人成功输入数值后,计算机用scanf(“%d,%d,%d,”,&a,&b,&c);
去读取到我们输入的数字,赋值给变量,一定一定要注意!冒号后面打上,和&变量名字!不然他怎么知道这些数字赋值给哪些字母呢?
然后,你就可以借助这些已经赋好值的字母们,完成你的计算,并且让计算机把他们摆到大屏幕上去。
printf(“(a+b)*c=%d”,(a+b)*c);
“”冒号里的字符串还可以后台改变,按照你想要的算法与计算机打好配合。
第二章 流程控制语句
第一节 顺序结构
从上往下依次执行
第二节 分支结构—if语句
if语句由if+(条件的逻辑表达式)+一行代码;或者一对{};内若干条语句组成。如果符合条件(表达式结果=1),就会执行后面{}内的语句。否则就会跳过不执行。通常我们用else(否则)去海纳其他情况。
if(关系表达式)
{
语句体A;
}
else if
{
语句体B;
}
else
{
语句体c;
}
eg.if(total>amount)
total+=amount+10; 只有一行代码,可以不加{}圈记
这上面一整行才是完整的if语句,加上结束标志;
嵌套if语句时,缩近格式并不能保障else的匹配,因为else总是和最近的if来匹配。保留必要的{},即使只有一条语句时。这样更好减少风险。
把多子集的部分的else与if格式排到同一行,
格式上就更加漂亮,不会排到特别右边。
书写代码时,注意先写if再写else情况,
别丢了{}
第三节 switch-case语句多路分支
else-if级联可以用于多路(多选项)的编程操作。因为它会一个一个往下走,不会跳过,直到满足if括号内的条件,进入操作。
然而switch-case语句可以一次性从case中找到符合的条件,速度大大提升。
switch(表达式){
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
…
default: 前面的都没实现就用default
语句体n;
break;
}
表达式:计算结果只能是字符/整数
case:值只能是字符/整数,不能是变量
case:值不能重复
break:表示中断,结束的意思,结束switch语句
当所有case都没有符合时,还有一个default。它干脆利落,直接选择。
结构
Int type=0
switch和if的区别:case用于有限个case进行匹配的情况,10个左右;if一般是对一个范围进行判断
第三节 循环结构用—重复去做某件事
while 循环
初始化语句
while(条件判断语句)
{
循环体语句
条件控制语句
}
比如数356是几位数,我们把它除以10,并不断地累积次数,直到无法再除为止,也就得到了它的次数。
356,35,3,0.+1+1+1=3
#include<stdio.h>
int main()
{ int x;
int n=0;
scanf("%d",&x);
n++;
x/=10;
if//while (x>0) {
n++;
x/=10;
}
printf("%d\n",n);
return 0;
}
检验过程,要运用多个边界值,去确保程序的正确
在程序中加入一些printf和here1,hr2,表明程序的步骤,检验过程哪里出错了。
do-while语句
do
{
循环体语句;
条件控制语句;
}while(条件控制语句);
不管三七二十一,先do一次代码,再判断条件是否进入循环。而while是先判断条件,再决定是否进入循环。核心要想清楚循环继续和循环终止的条件!
扩展:rand()
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(){
srand(time(0));
int number=rand()%100+1;
int count=0;
int a=0;
printf("我已经想好了一个1到100之间的数字。");
do{
printf("请猜这个1到100之间数:");
scanf("%d",&a);
count ++;
if(a>number){
printf("你猜的数大了。");
}else if(a<number){
printf("你猜的数小了。");
}
}
while(a!=number);
printf("太好了,你用了%d次就猜到了答案。\n",count);
}
for循环
for循环==while循环(都是先判断条件):
for(;条件;)==while(条件)
for(初始语句;条件判断语句;条件控制语句)
{
循环体语句
}
比如for(i=0; i<5; i=1+i){
printf(“%d”,i); }
初始语句:循环开始条件;条件判断语句:循环结束条件;条件控制语句:控制循环次数的;循环体语句:要重复执行的代码
变量只在所属的大括号中使用
for循环类似于一个计数循环:设定一个初始值,再设定一个临界范围。在这个区间内重复执行循环体,而每执行一轮循环,计数器值以一定步骤进行调整,如++1,--1。
对于一开始的i=0,当i<5时,重复做循环体,每一轮循环在做完循环体内语句后,使得i加1.
小套路:求和程序时,记录结果的变量应该初始化为0,而做求积的变量时,记录结果的变量应该初始化为1
for(int i=0; i<5; i=1+i) 在C99中,变量i的定义可以写在for语句内。
从问题到程序的思考。首先是变量,我们需要什么样的变量保存数据,包括保存用户的数据,显示结果的数据,表示变化的数据。齐次要思考程序的过程,是否需要循环,while,do-while还是for的选择以及循环的条件。
while 和for 的区别:
for循环中:知道循环次数或者循环的范围,比如累加求和1-100
while循环中:不知道循环的次数和范围,只知道循环的结束条件,比如读取文件中的内容,一次只能读取一个字母或汉字
无限循环
for( ; ; )
{
printf("hello world");
}
while (1)
{
printf("hello world");
}
do
{
printf("hello world");
} while (1);
跳转控制语句
break:不能单独书写,只能写在switch,或者是循环中,表示结束,跳出的意思
continue:结束本次循环,继续下次循环
有固定次数(人为可调定次数),用for;
必须执行一次,用do-while;
其他情况用while。
Break;跳出一整个循环体系
Continue;放弃了当前的循环语句,直接开启下一轮循环。
都只能对它所在的那一层循环做
循环跳出问题
break只能跳出单层循环,并且是最里面那个
goto:可以通过标号跳到任意地方,一般只用于跳出循环嵌套
比如:
for (int i = 1; i <=3; i++)
{
for (int j = 1; j <=3; j++)
{
printf("内循环执行%d\n",j);
goto a;
}
printf("内循环结束\n");
printf("-----------\n");
}
//标号
a:printf("外循环结束\n");
第三章 函数
第一节 什么是函数?
定义:程序中独立的功能
灵光一闪:找出a,b,c,d四个数中的最大值,可以先找出a,b之间的最大值,然后赋值给m,让m与c比较,较大值赋值给m,再让m与d比较,让较大值赋值给m
对于反复书写的代码,又不确定什么时候会用到的代码打包起来打包,要用就拿出来用
好处:提高代码的复用性和可维护性
第二节 函数的使用
格式要求
返回值类型 函数名 (形参1,形参2)
{
函数体;
return 返回值;
}
走流程:用输出语句描述,如何打一局游戏
void playGame() //要定义函数
{
函数体;
}
在最开始声明函数:void playGame();
调用函数:函数名();
带参数的函数
函数需要计算的参数不明确时,可以用形式参数和实际参数,注意二者必须一一对应
void sum(int num1,int num2)
{
int sum=num1+num2;
printf("%d\n",sum);
}
int main()
{
sum(20,30);
return 0;
}
再升级-------带返回值
int sum(int num1,int num2)
{
int sum=num1+num2;
return sum; //”return 返回值”结束函数+把后面的数据交给调用处
}
#include <stdio.h>
void sum(int num1,int num2); //调用函数
int main()
{
int a=sum(20,30); //定义变量去接收函数的结果
int b=sum(15,25);
return 0;
}
第三节 函数的使用细节
定义函数之前先问自己:
我定义函数是为了干什么事情?-----函数体
我干这件事需要干什么才能完成?-----形参
我干完了,调用处是否要继续使用?-----返回值类型
需要继续使用 返回值必须写
不需要返回 void
函数不调用就不执行
函数名不能重复
函数与函数是平级关系,不能嵌套定义
自定义函数写在main函数的下面,需要在上方声明
return下面不能编写代码,永远执行不到,无效
函数的返回值类型为void,表示没有返回值,return可以省略不写,如果写了,后面不能跟具体的数据,仅表示结束函数
第四节 常见函数
不同的函数需要导入的头文件不同
- <math.h>头文件里的函数
abs():获取绝对值
pow():幂 double a=pow(2,3);--------8.000000
sqrt():平方根
ceil():向上取整(进1法)
floor():向下取整(去尾法)
- <time.h>头文件里的函数
time():获取当前的时间
形参:表示获取的当前时间是否需要在其他地方进行存储。一般来讲,不需要-----time(NULL);
返回值:long long-----%lld
经典要求:获取随机数
int main(){
//设置种子
srand(time(NULL)); //用time()要导入<time.h>
//获取随机数
int num=rand(); //用rand要导入<stdlib.h>
//输出打印
printf(“%d\n”,num);
return 0;
}
两个小弊端:若种子不变,随机数结果是固定的
随机数范围没法直接指定
解决:
让种子不固定,用一个变化数据,即时间-----把时间当作种子
srand(time(NULL)); //用time()要导入<time.h>
默认范围是0~32767
若要控制范围:把这个范围变成包头不包尾,包左不包右,1~101
拿着尾巴-开头 101-1=100
修改代码
比如1--100
int num=rand()%100+1;
7--24
int num=rand()%17+7; //24-7=17
第五节 综合练习
(1)生成1—100之间的随机数,猜中为止
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand(time(NULL));
int num=rand()%100+1;
int guess; //猜的数字
while (1)
{
printf("请输入你要猜的数字:\n");
scanf("%d",&guess);
if (guess<num)
{
printf("小了");
}
else if (guess>num)
{
printf("大了");
}
else
{ printf("中了");
break;
}
}
return 0;
}
第四章 数组
第一节 定义和初始化
定义:是一种容器,可以用来存储同种数据结构的多个值
数组的初始化:定义数组的时候,第一次给数组赋值。数组类型 数组名 [长度]={数据值,数据值…}。连续的空间,一旦定义,数组长度不可改变。若数据值的个数<=长度,若是整数默认0,浮点数默认0.0,字符默认值\0;若大于,那么会报错
元素访问:运用索引,用数组名[索引]获取,打印用printf(“占位符”,数组名[索引]);
第二节 数组遍历和内存中的数组
数组遍历:利用for循环
内存中的数组:内存是软件在运行时,用来临时存储数据的;内存地址是内存中每一个小格子的编号,查看内存地址:int a=10;printf(“%p\n”,&a);。
数组常见问题:数组作为函数的形参,实际上传递的是数组的首地址,如果要在函数中对数组进行遍历的话,记得一定要把数组的长度一起遍历过去。
char s1[4]="asf\0";char s[4]={'a','s','f',\0};末尾默认有\0
sizeof():求变量所占的字节数—字符串
还可以用函数strlen(),与sizeof不同的是,strlen()求的是\0前面的长度
对应的是#include <string.h>
第三节 数组常见问题
strcpy:将一个字符串的内容copy到另一个。strcpy(s2,s1)表示将s1的内容复制s2里面去
strcat():连接。strcat(s2,s1)表示将s1的内容接到s2后面去
strcmp():字符串比较。printf("%d\n",strcmp(s1,s2));若二者长度相等,输出0,s1大于s2就输出1,s1小于s2就输出-1
做题技巧:(1)取最值,先拿一个数字,拿它遍历剩下的数组,遇到比它大的就取代:
//定义数组
int arr[]={22,8,33,6};
//定义变量max,记录数组的最大值。max的默认值一定要是数组中已经存在的值,一般是把0索引当作默认值
int max=arr[0];
//开始遍历------先确定数组长度
int len=sizeof(arr)/sizeof(int);
(2)生成10个1--100之间的随机数存入数组,求所有数据的和
//只知道要存10个数据,不知道具体值
int arr[10]={0}
你若填1,那么剩下9个数字默认0,一般默认全部是0
//生成随机数,导入
#include <stdlib.h>
#include <time.h>
//设置种子
srand(time(NULL));
//生成随机数
int num=rand() %100+1;
建议刚开始一个循环干一件事
//若题目要求生成的随机数不能重复,判断num在函数中是否存在,存在则返回。使用它记得先声明
int contains(int arr[],int len,int num)
{
for (int i = 0; i < len; i++)
{
if(arr[i]==num)
{
return 1;
}
}
return 0;
}
(3)反转数组
思路:定义i指向数组最小的索引,j指向最大的索引,i,j位置交换元素,然后i-1,j-1交换,知道i,j相遇,或者i跑到了j的右边
int i=0;
int j=len -1;
while (i<j)
{
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
i++;
j--;
}
(4)打乱数组中的数据
定义一个数组,存入1--5,要求打乱数组中所有数据的顺序
思路:遍历数组一次得到每一个数据,与随机索引进行数据交换。开始获取到0索引,再获取一个随机的索引,进行数据交换,i++;再用1索引
//遍历数组,得到每一个元素,让这个元素与随机索引处元素进行交换
srand(time(NULL));
for (int i = 0; i < len ; i++)
{
//获取一个随机索引:0--4
int index=rand()%len;
//拿着i指向的元素,跟index指向的元素进行交换
int temp=arr[i];
arr[i]=arr[index];
arr[index]=temp;
}
第四节 数组算法
查找算法
基本查找(顺序查找):把一堆数放在数组中,从数组的0索引开始依次往后查找,找到了就返回索引
二分查找:使用前提:数组中的数据必须有序,核心逻辑是每次排除一半的查找范围。min和max表示要查找的范围,mid是在min和max中间,如果要查找的元素在mid左边,缩小范围时,min不变,max等于mid-1;在右边,max不变,min等于mid+1
插值查找:计算mid时精度高,但要求数据有序并尽可能均匀
mid=min+((key-arr[min])/(arr[max]−arr[min]))∗(max−min)
分块查找和哈希查找:无序中又透露着有序
7,10,13,19,16,20,27,22,30,40,36
分块原则:前一块中的最大数据,小于后一块中发所有数据(块内无序,块间有序)
块数数量一般等于数字的个数开根号
核心思路:先确定要查找的元素在哪一块,然后在块内挨个查找
第五章 指针
第一节 什么是指针
指针就是内存地址。用内存地址可以找到对应的小空间,获取到这个空间里面的数据,还可以对它进行修改
一般用变量把指针存起来,这个变量就是指针变量,简称指针
指针变量定义格式:数据类型*变量名。数据类型要跟指向变量的类型保持一致(int a=10;int*p=&a;),*就是标记,变量名就是你自己起的名字。此处指针变量的名字是p,
第二节 指针的作用
作用:
查询数据。eg用*p(这里的*是解引用运算符,表示通过后面的内存地址去获取到对应的数据)去获取变量a中的数据,printf(“%d\n”,*p);
存储数据:若写*p=200;表示存储数据或修改数据
内存管理:指针变量占用的大小,和数据类型无关,跟编译器有关
参数传递:操作其他函数中的变量,
int main()
{
int a=10;
int b=20;
printf("调用前:%d %d\n",a,b);
swap(a,b);
printf("调用后:%d %d",a,b);//打印发现还是原来的值
return 0;
}
void swap(int num1,int num2)
{
//仅仅交换的是num1和num2中的值,影响不了a和b的值
int temp=num1;
num1=num1;
num2=temp;
}
这个时候用指针
int main()
{
int a=10;
int b=20;
printf("调用前:%d %d\n",a,b);
swap(&a,&b); //传给函数的是a,b的内存地址
printf("调用后:%d %d",a,b);//打印发现还是原来的值
return 0;
}
void swap(int *p1,int *p2)
{
//此时交换的是p1指向的变量和p2指向的变量的值,即a和b
int temp=*p1;
*p1=*p2;
*p2=temp;
}
细节:函数中变量的生命周期跟函数相关,函数结束了,变量也会消失。此时在其他函数中,就无法通过指针使用了。如果不想函数中的变量被回收,可以在变量前加static关键字
给函数返回多个值,详细见2.0.c
函数的结果和计算状态分开
方便操作数组和函数
第三节 指针高级
指针的计算
int a=10;
int* p=&a;
指针的数据类型的作用:获取字节数据的个数
步长:指针移动一次的字节个数。char:1 short:2 int:4(一个步长四个字节) long:4 long long:8
有意义的操作:
指针跟整数进行加减操作:(每次移动n个步长)
用数组保证内存空间是连续的
加法:指针往后移动了n步 p+1
减法:指针往前移动了n步 p-1
指针跟整数进行减操作:(间隔步长)
无意义的操作:
指针跟整数进行乘除操作
因为此时指针指向不明
指针和指针进行加乘除操作
野指针和悬空指针
野指针:指针指向的空间未分配(单机游戏里的外挂)
悬空指针:指针指向的空间已分配,但是被释放了
不要使用,数据可能是错的
没有类型的指针:void类型指针
只能记录地址值,不能获取变量里的数据,也不能计算
不同类型的指针之间,是不能相互赋值的
但void类型是个例外,因为void没有任何类型,好处是可以接受任意类型指针记录的内存地址
根据特性交换数据函数可以用void类型,更具通用性
举个例子:
#include <stdio.h>
void swap(void* p1,void* p2,int len);
int main()
{
//调用函数交换数据
long long c=100l;
long long d=200l;
swap(&c,&d,8);
printf("c=%lld,d=%lld\n",c,d);
return 0;
}
void swap(void* p1,void* p2,int len)
{
char* pc1=p1;
char* pc2=p2;
char temp=0;
//以字节为单位,一个字节一个字节进行交换
for (int i = 0; i < len ; i++)
{
temp=*pc1;
*pc1=*pc2;
*pc2=temp;
pc1++;
pc2++;
}
}
二级指针和多级指针
本身也是变量,只是记录的是内存地址
二级指针:指向指针的指针
此时二级指针数据类型就不是int了,跟指向空间中,数据的类型保持一致,是内存地址,可以用int*表示,比如int**b=10;
作用:二级指针可以操作一级指针记录的地址
数组和指针
数组指针:指向数组的指针
方便操作数组中各种各样的数据
获取数组的指针,实际上获取的是数组的首地址
举个例子:
int arr[]={10,20,30,40,50};
int len=sizeof(arr)/sizeof(int);
//方法1:获取数组的指针,实际上获取的是数组的首地址
int* p1=arr;
int* p2=&arr[0];
printf("%d\n",*p1);
printf("%d\n",*(p1+1));
//方法2:利用循环和指针遍历数组获取里面的每一个元素
for (int i = 0; i < len; i++)
{
printf("%d\n",*p1++);
}
细节:arr参与计算时,会退化为第一个元素的指针,记录的内存地址第一个元素的首地址,也是数组的首地址
特殊情况:
&arr获取地址的时候,不会退化,记录的内存地址第一个元素的首地址,也是数组的首地址
sizeof运算的时候,不会退化,arr还是整体
int arr[]={1,2,3,4,5};
int*p1=arr; //步长为4
int*p2=&arr; //步长为20:步长=数据类型*数组长度
索引操作
二维数组的定义:把多个小数组,放到一个大的数组中
定义格式:格式1:
//m表示二维数组的长度,n表示一维数组的长度
数据类型 arr[3][5]=
{
{1,2,3,4,5},
{11,22,33,44,55},
{111,222,333,444,555}
};
格式2:
//定义两个一维数组
int arr1[5]={1,2,3,4,5};
int arr2[5]={6,7,8,9,10};
//把两个一维数组放到二维数组当中
int* arr[2]={arr1,arr2} //数组的数据类型,跟内部存储的元素类型保持一致。此时二维数组存的是一维数组的内存地址,所以写int*
(1)利用索引遍历第一种格式的二维数组
弊端:要求每个二维数组长度一致
for (int i = 0; i < 3; i++) //i是二维数组中的索引
{
for (int j = 0; j < 5; j++) //j是一维数组中的索引
{
{
printf("%d",arr[i][j]);
}
}
(2)利用索引遍历第二种格式的二维数组
先把所有的一维数组定义完毕,再放到二维数组中
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* arr8[3]={arr1,arr2,arr3};
for (int i = 0; i <3; i++)
{
int len=sizeof(arr8[i])/sizeof(int); //len算出来是2,因为使用数组名进行计算时,会退化为指向第一个元素的指针,64位win中为8个字节,8/4=2
for (int j = 0; j <len; j++)
{
printf("%d\t",arr8[i][j]);
}
printf("\n");
}
结果:遍历失败
1 2
1 2
1 2
改为:
for (int i = 0; i <3; i++)
{
//预先计算每一个数组的真实长度
int len1=sizeof(arr1)/sizeof(int);
int len2=sizeof(arr2)/sizeof(int);
int len3=sizeof(arr3)/sizeof(int);
//再定义一个数组,装所有数组的长度
int lenArr[3]={len1,len2,len3};
for (int j = 0; j <lenArr[i]; j++)
{
printf("%d\t",arr8[i][j]);
}
printf("\n");
}
(3) 利用指针遍历第一种格式的二维数组
int arr[3][5]=
{
{1,2,3,4,5},
{11,22,33,44,55},
{111,222,333,444,555}
};
数组内部元素的类型:数据类型*指针名=arr--------int(*p)[5]=arr;
(二维数组里面存储的是一维数组int[5])
举个例子:
//获取二维数组的指针
int(*p)[5]=arr;
for (int i = 0; i < 3; i++)
{
//遍历一维数组
for (int j = 0; j < 5; j++)
{
printf("%d\t",*(*p+j)); //(*p+j)获取的是上面三个一维数组的指针
}
printf("\n");
//移动二维数组的指针,继续遍历下一个一维数组
p++;
}
(4) 利用指针遍历第二种格式的二维数组
数据类型*指针名=arr---------int* *p==arr;
和上一个的区别是:上一个二维数组存的是一维数组,这个二维数组存的是两个内存地址,即int*
举个例子:
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* arr8[3]={arr1,arr2,arr3};
int**p=arr8;
for (int i = 0; i <3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d\t",*(*p+j));
}
printf("\n");
//移动指针
p++;
}
辨析:数组指针和指针数组
前者是指向数组的指针,方便操作数组中各种各样的数据。比如:int* p=arr;int(*p)[5]=&arr;
后者是存放指针的数组。比如int *p[5],这个数组里存着int类型的指针
函数和指针
格式:返回值类型 (*指针名)(形参列表)
作用:利用函数指针,可以动态的调用函数
方法:举个例子
void method1();
int method2(int num1,int num2);
int main()
{
//函数指针
//定义指针指向两个函数
void (*p1)()=method1; //指针名字直接由函数变过来
int (*p2)(int,int)=method2;
//利用函数指针去调用函数
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;
}
第六章 字符串
第一节 定义方式
C语言的三种数据类型
整数:short,int,long,long long
小数:float,double
字符:char
在底层,对于字符串”asdf”,会拆成字符数组’a’’s’’d’’f’’\0’进行存储,默认在末尾加上’\0’
书写方式:char str1[4]=”abc”; //字符数组+双引号
char* str=”abc”; //指针+双引号
数组的长度要么不写,写的话要预留多一些
对于第一种书写方式,字符串内容可改变,比如str1=’A’;
第二种书写方式:
会把底层的字符数组放在只读常量区,只读常量区内容不可修改,里面的字符串可以复用
举个例子:
char str[100];
printf("请输入一个字符串:\n");
scanf("%s",&str); //字符串输入&可写可不写
经典要求:遍历字符串
char str[100];
printf("请输入一个字符串:\n");
scanf("%s",&str);
char*p=str;
while (1)
{
char c=*p;
//用指针获取字符串中每一个字符
if (c=='\0') //字符串末尾会默认加‘\0’
{
break;
}
printf("%c\n",c);
p++;
}
第二节 字符串数组
字符串的底层其实就是字符数组,把多个字符数组,再放到一个大的数组当中,即二维数组
遍历:
char strArr[5][100]=
{
"ffs",
"ffff",
"dfsf",
"fssf"
};
//用二维数组的方式进行遍历
for (int i = 0; i < 4; i++)
{
char* str=strArr[i];
printf("%s\n",str);
}
//用指针的方式进行遍历
//把5个字符串的指针再放到一个数组中
char* strArr2[4]={
"ffs",
"ffff",
"dfaf",
"fssf"
};
//遍历指针数组
for (int i = 0; i < 4; i++)
{
char* str=strArr2[i];
printf("%s\n",str);
}
第三节 字符串常用函数
先导入#include <string.h>
strlen:获取字符串的长度。在统计长度的时候,是不计算结束标记的,一个中文默认两个字节
strcat:拼接两个字符串。strcat(str2,str3); -----把str3加到str2的屁股后面。所以要先保证第一个字符串可以修改,第一个字符串剩余的空间可以容纳拼接
的字符串
strcpy:复制字符串。strcat(str2,str3);--------把str3复制给str2,把原来的str2取代。要先保证第一个字符串可以修改,第一个字符串剩余的空间可以容纳拼接
的字符串
strcmp:比较两个字符串。int res=strcmp(str1,str2);----------字符串内容和顺序完全一样:0,否则非0
strlwr:将字符串变成小写。strlwr(str2);
strupr:将字符串变成大写。strupr(str2);
详细见3.0.c
第七章 结构体
第一节 定义
自定义的数据类型,一批数据组合而成的结构型数据,里面的每一个数据都是结构体的“成员”
格式:
struct 结构体名字
{
成员1;
成员2;
……
};
可写函数里面(局部位置)或外面(全局位置)
举个例子:
#include <stdio.h>
#include <string.h>
struct Girl
{
char name[100];
int age;
char gender;
double height;
};
int main()
{
//使用结构体,定义一个女生类型的变量
struct Girl gf1;
strcpy(gf1.name,"小张");
gf1.gender='F';
gf1.age=18;
gf1.height=1.72;
printf("我的名字为:%s\n",gf1.name);
printf("我的年龄为:%d\n",gf1.age);
printf("我的性别为:%c\n",gf1.gender);
printf("我的身高为:%lf\n",gf1.height);
return 0;
}
第二节 起别名
typedef struct Girl
{
成员1;
成员2;
}别名;
举个例子:
typedef struct Ultraman
{
char name[100];
int attack;
int defense;
int blood;
}M;
int main()
{
M taro={"泰罗",100,90,500};
M rem={"雷欧",90,80,450};
M arr[2]={taro,rem};
for (int i = 0; i <2; i++)
{
M temp=arr[i];
printf("奥托曼的名字是:%s,攻击力是:%d,防御力:%d,血量是:%d\n",temp.name,temp.attack,temp.defense,temp.blood);
}
return 0;
}
第三节 结构体作为函数的参数进行传递
函数中可以传递结构体,两种情况:传递结构体中的数据值,传递结构体的地址值
举个例子:
第一种情况
//结构体作为函数的参数进行传递,定义一个函数,修改学生中的数据
#include <stdio.h>
#include <string.h>
typedef struct Student
{
char name[100];
int age;
}S;
void method(S st);
int main()
{
S stu;
//给学生赋初始值
strcpy(stu.name,"aaa");
stu.age=0;
printf("学生的初始化数据是:%s,%d\n",stu.name,stu.age);
method(stu);
printf("学生的信息修改为:%s,%d",stu.name,stu.age);
return 0;
}
//如果结构体中写的是结构体类型的变量,相当于是定义了一个新的变量
//如果把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.age));
printf("在method函数中修改之后,学生的:%s,%d\n",st.name,st.age);
}
打印结果:
学生的初始化数据是:aaa,0
接受到main函数中学生的初始化数据是:aaa,0
请输入要修改的学生名字:
aaa
请输入要修改的学生年龄:
78
在method函数中修改之后,学生的:aaa,78
学生的信息修改为:aaa,0
怎么改呢:
第二种情况:
//如果要在函数中修改stu的值,此时就不要再定义新的变量了
//直接接受stu的内存地址,通过内存地址就可以修改stu中的数据了
//指针p里面记录的是main函数中stu的内存地址
void method(S* p)
{
printf("接受到main函数中学生的初始化数据是:%s,%d\n",(*p).name,(*p).age);
//修改
printf("请输入要修改的学生名字:\n");
scanf("%s",(*p).name);
printf("请输入要修改的学生年龄:\n");
scanf("%d",&((*p).age));
printf("在method函数中修改之后,学生的:%s,%d\n",(*p).name,(*p).age);
}
函数调用写为:method(&stu);
第四节 结构体的嵌套
举个例子:
/*定义一个结构体表示学生Student,成员如下:名字,年龄,身高,性别,联系方式,
联系方式:Message也是一个结构体,成员如下:手机号,电子邮箱*/
struct Message
{
char phone[12];
char mail[100];
};
struct Student
{
char name[100];
int age;
char gender;
int height;
struct Message msg;
};
int main()
{
struct Student stu;
strcpy(stu.name,"zhangsan");
stu.age=23;
stu.gender='M';
stu.height=1.78;
strcpy(stu.msg.phone,"15869765284");
strcpy(stu.msg.mail,"15869764@qq.com");
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={"lili",24,'F',1.65,{"752456565","565689@qq.com"}};
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;
}
第五节 内存对齐
以下结构体占用24个字节,不是14:
struct num
{
double a; 8
char b; 1+3个空白位
int c; 4
char d; 1+7个空白位
};
确定变量位置:只能放在自己类型的整数倍上
中间的空位C语言会自动填补空白字节;最大一个补位:结构体的总大小,是最大类型的整数倍
你以为是这种:
其实是这种:
简单地说就是只能放在自己类型整数倍的内存地址上。int-----4;double-----8;long long------8;char-------1。为了节省空间,把小的数据类型,写在最上面,大的写在最下面
第八章 文件
第一节 概述
操作方式:读取数据到内存(输入流),把内存数据写到文件中(输出流)
路径:文件在电脑中的位置
绝对路径(以盘符开始):C:\User\admin\Desktop\a.txt
相对路径:aaa\a.txt
转义字符-------\
表示字符串的开头或结尾,即表示改变\后面这个符号本身的含义,把他变成一个普普通通的双引号
举个例子,打印路径应该写
int main()
{
char* file="E:\\aaa\\a.txt";
printf("%s\n",file);
return 0;
}
第二节 读取+写出数据
读取数据
把本地文件中的数据,读到程序中来
打开文件:fopen
只读模式:FILE* file=printf(“E:\\aaa\\a.txt”,”r”);
读数据:fgets(一次读一行,读不到返回null),fgetc(一次读一个,读不到返回-1),fread(一次读多个)
举个例子:
int c=fgetc(file);
print(“%c”,c);
char arr[1000];
char* str=fgets(arr,1000,file);
printf(“%s”,str);
关闭文件:fclose
写出数据
把程序的数据,写到本地文件永久存储
fputc,fputs,fwrite