C语言基础知识

目录

第一章 基础语法... 3

第一节 第一个C程序... 3

第二节 关键字... 4

第三节 数据类型... 4

第四节 运算符和表达式... 4

第五节 变量函数... 7

第二章 流程控制语句... 8

第一节 顺序结构... 8

第二节 分支结构—if语句... 8

第三节 switch-case语句多路分支... 8

第三节 循环结构用—重复去做某件事... 10

第三章  函数... 14

第一节 什么是函数?... 14

第二节 函数的使用... 14

第三节 函数的使用细节... 16

第四节 常见函数... 16

第五节 综合练习... 18

第四章  数组... 19

第一节 定义和初始化... 19

第二节 数组遍历和内存中的数组... 19

第三节 数组常见问题... 19

第四节 数组算法... 22

第五章  指针... 23

第一节 什么是指针... 23

第二节 指针的作用... 23

第三节 指针高级... 24

指针的计算... 24

二级指针和多级指针... 26

数组和指针... 26

函数和指针... 30

第六章 字符串... 32

第一节 定义方式... 32

第二节 字符串数组... 33

第三节 字符串常用函数... 33

第七章 结构体... 35

第一节 定义... 35

第二节 起别名... 36

第三节 结构体作为函数的参数进行传递... 36

第四节 结构体的嵌套... 38

第五节 内存对齐... 40

第八章 文件... 41

第一节 概述... 41

第二节 读取+写出数据... 41

第一章 基础语法

第一节 第一个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缩进推进去

第二节 关键字

  1. 数据类型的关键字

char(%c,定义字符类型的变量用的:char ch=’a’。打印用%c),float(%f,单浮点型,带f的变量),double(%lf,双浮点型,精度更高),int(%d,定义整型变量),short(短整型short int a=11;)。

  1. 控制语句的关键字

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;比如这个,变量cdouble浮点量,那么等号左边也应该搞浮点运算,要么a,bdouble,要么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

 

 switchif的区别: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语句内。

从问题到程序的思考。首先是变量,我们需要什么样的变量保存数据,包括保存用户的数据,显示结果的数据,表示变化的数据。齐次要思考程序的过程,是否需要循环,whiledo-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可以省略不写,如果写了,后面不能跟具体的数据,仅表示结束函数

第四节 常见函数

不同的函数需要导入的头文件不同

  1. <math.h>头文件里的函数

abs():获取绝对值

pow():幂               double a=pow(2,3);--------8.000000

sqrt():平方根

ceil():向上取整(进1法)
floor():向下取整(去尾法)
 

  1. <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)

分块查找和哈希查找:无序中又透露着有序

71013191620,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,因为使用数组名进行计算时,会退化为指向第一个元素的指针,64win中为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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值