C语言从入门到放弃
- 基础部分
-
- C语言概述
- C语言程序的组成
- 原码、反码、补码
- 进制转换
- C语言的数据类型
- 数据常量与数据变量
- [printf 与 scanf函数](#printf 与 scanf函数)
- 运算符和表达式
-
- 常用运算符
-
- [赋值运算符 =:(等于)](#赋值运算符 =:(等于))
- [算术运算符 +、-、*、/、%:(加减整除、取余)](#算术运算符 +、-、*、/、%:(加减整除、取余))
- [关系运算符 >、<、>=、<= 、==、!=:(大于、小于、大于等于、小于等于、等于等于、不等于)](#关系运算符 >、<、>=、<= 、==、!=:(大于、小于、大于等于、小于等于、等于等于、不等于))
- [逻辑运算符 &&、||、!](#逻辑运算符 &&、||、!)
- [复合运算符 :+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=、~=](#复合运算符 :+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=、~=)
- 自加自减运算符:++、--
- 逗号运算符:","
- [条件运算符:" ? : " 唯一三目运算符](#条件运算符:“ ? : ” 唯一三目运算符)
- 数据类型转换
- C语言基本结构
- 九大语句
- 函数
- 数据结构
- 入门部分
基础部分
C语言概述
与C++的面向对象不同,C是面向过程的语言。也就是需要先分析出解决问题的步骤,然后一步步的去编写程序来实现。
比如用代码实现控制一个机器人去行走:
C:先抬高一条腿多少距离,然后向前跨跃多少距离,落脚,循环;
C++:调用对象中的行走函数(这部分也需要面向过程实现)
C语言一般能够与编写底层硬件的驱动程序,操作系统等。
与汇编相比较,C语言 ,编写操作系统具有天然优势,可以大大缩小开发周期。但是C代码可维护性较低,可复用性也比较低。
C语言程序的组成
- 主函数
main函数:是程序的入口地址,程序在运行时会主动去寻找并且执行这个函数,一旦主函数运行完成,整个程序也将停止运行。
主函数名必须为main
主函数有且只有一个
- 头文件
头文件是存放被调用的功能函数的声明,可以让编译器知道那些函数可以被使用,头文件不会被执行,在预处理阶段就已经被包含替换了。
c
#include <stdio.h> //头文件包含语句
int main()
{
//主函数
printf("hello C\n");
return 0;
}
C语言指挥运行 {} 内的程序代码,每一句C代码的结束都需要 " ; " 来做标志。
那么界限来你就可以去尝试编写一个C程序了。
C文件在Linux下的编译与运行
- 在文件夹中创建一个c文件:touch 或者 gedit
- 编写程序并且保存
- 使用GCC编译器进行编译:
gcc xxx.c -o yyy
xxx文件名 yyy生成物名,如果不写默认为a.out - 使用命令./yyy 运行生成物
预编译处理
预编译处理也称为编译预处理,是在程序正式编译之前需要进行的前期编译处理阶段。主要作用是向编译器传递信息。以井号("#")开头的命令都是编译预处理命令,并且编译预处理命令后面没有分号(";")。
c
编译预处理命令格式:gcc -E XXXX.c -o XXXX.i
原码、反码、补码
计算机中的最小存储单位是比特(bit),通常情况下计算机是按照字节去存储信息(1字节=8比特),一个bit能够存储两种信息(0或者1),一个字节能够存储2的8次方个不同的信息。
在什么规则都不考虑的情况下:
1+2 = 0000 0001 + 0000 0010 = 0000 0011 = 3
1-1 = 0000 0001 - 0000 0001 = 0000 0000 = 0
但是计算机并不认识加减符号因此则引入了原码、反码、补码dee 概念。
反码:正数与不变负数为取反,同时最高位变为符号位。
1 + (-1) =(原码) 0000 0001 + 1000 0001 =(反码) 0000 0001 + 1111 1110 =(反码) 1111 1111 =(原码) 1000 0000 = -0 = 0
人理解 +0与-0是一样的东西,但是计算机很难做到这个,因此诞生了补码。
补码: 正数不变,负数是反码+1.
1 + (-1) =(原码) 0000 0001 + 1000 0001 =(反码) 0000 0001 + 1111 1110 =(补码) 0000 0001 + 1111 1111 = 0000 0000(高位溢出) = 0
这样计算机中只需要有假发计算器就可以解决,同时补码也解决了+0、-0的问题。
进制转换
进制就是计数方法,比如我们最常用的十进制,也就是满十进一。
二进制 :
十进制转二进制只需要一直除以2即可,比如21的二进制是 0001 0101
二进制转十进制公式如下,我们依旧以21为例子:1x20 + 0x21 + 1x22 + 0x23 + 1x24 = 1 + 4 + 16 = 21
其他进制计算类推即可,其中十六禁止的10~15被替换为字母ABCDEF。
C语言的数据类型
整型 Int :代表整数,长度为4字节
有符号整型 signed int 等价于int,存储范围-231 ~ + 231
无符号整型:unsigned int ,存储范围 0 ~ 232
短整型 short :代表整数,长度为2字节
长整型long:代表整数,长度为4字节
其中int是默认类型,运算效率高,而long基于不同的平台长度会变化比如64位的机器上long就是8字节
长长整型 long long:代表整数,长度为8字节
浮点型 float :代表小数,长度为4字节,可以精确到小数点后7位
双浮点型 double :代表小数,长度为8字节,可以精确到小数点后16位
字符型 char:存放ASC II 码的一种类型,长度为一字节
数据常量与数据变量
数据常量 : 指不可改变的数据,比如 1,2.2,-1,160这种
数据变量 : 指在程序运行的过程中可以改变数值的变量,一般使用一个名称或者标识符来表示一个变量,变量使用前要先定义
变量的命名规范
1) 变量名组成:可以由字母(严格区分大小写)、数值、下划线以及美元符号($)组成。
2) 变量名开头:不能以数字开头。
3) 变量重名:不能与关键字重复(重名)。
4) 在定义变量名的时候,遵循"望文生义"的原则,尽量使用突出"内容"、"功能"以及"含义"等词汇来对变量进行命名。
c
include <stdio.h>
int main()
{
int A; //对
//int 4GCount; //不对
int _4GCount; //对
int $4GCount; //对
//int int; //不对
int a; //不同与A
int A_B; //对
int A$B; //对
int _12345678912345678912345678912wertyuidfghkfghjfghjkfghjfghjfghgh3456789;//对
return 0;
}
变量命名规则:
基本原则是:变量名=属性+类型+对象描述;
其中每一对象的名称都要求有明确含义,可以取对象名字全称或名字的一部分。要基于容易记忆容易理解的原则。保证名字的连贯性是非常重要的。"
属性部分:全局变量 g_
常量 m_
局部变量 l_
静态变量 s_
类型部分:指针 p
函数 f
无效 n
长整型 l
布尔 b
浮点型(有时也指文件)f
双字 dw
字符串 sz
短整型 n
双精度浮点 d
计数 c(通常用cnt)
字符 ch (通常用c)
整型 i(通常用n)
字节 by
字 w
无符号 u
描述部分:最大Max 最小Min 初始化Init 临时变量T (或Temp) 源对象Src 目的对象Dest。
驼峰命名法
驼峰命名法:是一种命名规范,广泛用于各种计算机开发场景中。帮助开发人员清晰的命名各个文件。
1.分类•大驼峰命名法 •小驼峰命名法
2.使用这种命名法长得很像骆驼的驼峰,因此叫做驼峰命名法。
驼峰命名法指的是将文件名通过英文命名,除了第一个单词以外,其他单词的第一个首字母都为大写状态。
int nameArray; //小驼峰命名法
int NameArray; //NameArray 大驼峰命名法
大驼峰和小驼峰的区别在于,第一个单词的首字母是否大写,如果大写,则为大驼峰命名,反之,为小驼峰。
printf 与 scanf函数
prrintf是输出函数,用于输出内容,scanf是读取函数,可以读取键盘等输入设备的输入信息:
c
#include <stdio.h>
int main()
{
float l_fnum=10.0;
double l_dnum=10.0;
printf("以%%d打印数据:以十进制格式输出:\n");
printf("%d\n",12);
printf("以%%u打印数据:以无符号整形格式输出:\n");
printf("%u\n",12);
printf("以%%x打印数据:以十六进制格式输出:\n");
printf("0x%x\n",12);
printf("以%%o打印数据:以八进制格式输出:\n");
printf("0%o\n",12);
printf("以%%c打印数据:以字符格式输出:\n");
printf("%c\n",50);
printf("以%%s打印数据:以字符串格式输出:\n");
printf("%s\n","hello"); //%s需要使用到字符串首地址
printf("以%%f打印数据:以浮点型格式输出:\n");
printf("%f\n",l_fnum);
printf("以%%lf打印数据:以双精度浮点型格式输出:\n");
printf("%lf\n",l_dnum);
printf("以%%.nlf打印数据:以双精度小数点后n位浮点型格式输出:\n");
printf("%.20lf\n",l_dnum);
printf("以%%nd打印数据:以十进制格式输出 占n位:\n");
printf("%10d\n",12);
printf("以%%0nd打印数据:以十进制格式输出 占n位空位补0:\n");
printf("%010d\n",12);
printf("以%%p打印数据:地址值:\n");
printf("%p\n",&l_dnum);
int l_iinput=0;
scanf("%d",&l_iinput);//要想对变量进行赋值操作,需要知道变量首地址,这一步为改变变量内容
//无需了解变量内容
printf("l_iinput:%d\n",l_iinput);
int l_iinput1=0;
int l_iinput2=0;
int l_iinput3=0;
printf("请输入3个整形数值:\n");
scanf("%d%d%d",&l_iinput1,&l_iinput2,&l_iinput3);//要想对变量进行赋值操作,需要知道变量首地址,这一步为改变变量内容,无需了解变量内容
printf("l_iinput1:%d %d %d\n",l_iinput1,l_iinput2,l_iinput3);
return 0;
}
运算符和表达式
运算符 的本质是一个符号,一个能够进行运算的特殊符号。根据运算符进行运算操作数的数量,把运算符
分为以下几种类型:
1)单目运算符:又被称为"一元运算符",是指在运算过程中只需要一个操作数的运算符。
2)双目运算符:是指在运算过程中只需要两个操作数的运算符。
3)三目运算符:是指在运算过程中需要三个操作数的运算符。 在C语言中,只有一个三目运算符:" ? :"
表达式 的本质是一个式子,在C语言中的定义是用C语言运算符将运算对象连接起来的式子就叫表达式。
使用的时候需要注意3点:
① 表达式的最终结果只能有一个。
② 在程序中遇到表达式的时候,必须先计算出表达式的结果或值。
③ 在计算表达式的时候,需要考虑运算符的优先级、结合性以及操作数等条件。
常用运算符
赋值运算符 =:(等于)
(1)运算符的作用: 赋予数值,写入到内存空间。
(2)运算符格式: 变量 = 变量、变量 = 常量、变量 = 表达式
(3)运算符结合性: 右结合性,即先计算运算符右边的变量或表达式的值,写入左边的内存空间。
算术运算符 +、-、*、/、%:(加减整除、取余)
(1)运算符作用: 四则运算
(2)算数运算符3条操作原则:
① 整型跟整型运算:结果只能是整数,不会得到浮点型数据。
②求余运算:浮点型不能进行求余运算。
③ 类型不同:在C语言中,只有类型相同的数据才能进行算术运算,如果类型不同,低精度的数据类型会自动向高精度的数据类型转换,数据类型精度从低到高排列:char -- short -- int -- long -- float -- double -- long double。
关系运算符 >、<、>=、<= 、==、!=:(大于、小于、大于等于、小于等于、等于等于、不等于)
(1)运算符作用: 判断数据之间的关系(大小)
(2)运算符格式:(变量/表达式/常量) 运算符 (变量/表达式/常量) 双目运算符
(3)关系运算符4条操作规则:
① 关系运算符的运算结果:逻辑结果:逻辑真(1)、逻辑假(0)
逻辑结果:真默认采用1表示 逻辑假默认采用0表示
真假说明: 非零值即为真(1真 2真 -1真 -2真) 假:0
② 关系运算符的数据:关系运算符运算的时候只关心数据的大小,不关心数据的类型。
数据大小是关系运算符唯一评判标准;
③ 关系运算符跟算术运算符混合使用:先计算后判断。
由于算数运算优先级处于3 4优先级,关系运算符优先级处于6优先级;先运算后判断
④ 关系运算符应用场合:常用于条件选择
逻辑运算符 &&、||、!
(1)运算符作用:多个条件的判断,得到多个条件的逻辑结果。逻辑运算符的运算结果为逻辑值:真(1),假(0)
(2)逻辑运算符操作原则:
逻辑与(&&) :并且关系
① 逻辑与运算符格式:变量1或表达式1 && 变量2或表达式2 && ...... && 变量n或表达式n
② 逻辑与运算符规则: 所有变量或表达式的值都为真时,整体为真;有一个为假,整体为假
③ 逻辑与运算过程: 首先计算变量1或表达式1的值,判断真假,如果为假,整体为假,后边的变量或表达式都不再进行计算和判断;如果为真,继续判断变量2或表达式2的值,以此类推,直到所有的值都为真,整体为真,或者有一个为假,整体为假。
逻辑或(||) :或者关系
① 逻辑或运算符格式:变量1或表达式1 || 变量2或表达式2 || ...... || 变量n或表达式n
② 逻辑或运算符规则:所有变量或表达式的值都为假时,整体为假;有一个为真,整体为真
③ 逻辑或运算过程: 先计算变量1或表达式1的值。如果为真,整体为真;后边变量或表达式
不在进行计算和判断。如果为假,继续判断变量2或表达式2的值,以此类推,直到所有的值都为假,整体为假,或者有一个为真,整体为真。
逻辑非(!) :取反关系
① 逻辑或运算符格式:!数据或表达式
② 逻辑非运算规则: 真变假,假变真。
复合运算符 :+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=、~=
(1)运算符作用:赋值运算符与其他双目运算符结合的写法
(2)运算符作用格式: 变量 复合运算符 变量或常量或表达式(右结合性)
(3)运算符使用原则:先计算运算符右边的变量或表达式的值,然后带入左边进行计算。
例 int A =3; A+=2; A = A+2; A=5;
自加自减运算符:++、--
(1)运算符作用:让变量的值自加或自减1
(2)运算符格式:变量++(后置自增)、++变量(前置自增)、变量--(后置自减)、--变量(前置自减)
(3)运算符位置:
① 运算符后置A++:先使用变量的值,语句执行结束之后,在让变量加1;
② 运算符前置++A: 先让变量的值加1,在执行语句。
前置自增与后置自增区别:
①由于前置自增没有新空间开辟,造就了前置自增效率要比后置自增高;
②设计方式:应用场景 先引用/先计算 先引用->临时变量(不节省空间)
(4)自加自减运算符3条操作规则:
① 自加或自减运算符可以操作的类型:可以操作整型数据,也可以操作浮点型数据(常用于整型)。(浮点型进行前置自加自减或者自加自减只能对整数部分进行操作)
② 自加或自减运算符对内存空间的操作过程:先读出变量的值,自加或自减之后,再写入内存空间。
③ 遇到自加自减运算符时的处理方法:先把自加自减运算符剥离出来,如果在前(在变量之前),就让变量加1或减1之后再参与运算;如果在后(在变量之后),先进行运算,再让变量自加或自减1。
逗号运算符:","
(1)运算符作用:将多条语句变成一条语句
(2)运算符格式:语句1,语句2,......,语句n
(3)运算符操作原则:
① 运算方向:从左到右依次运算,所有的表达式都要运算
② 运算结果:最后一个变量或表达式的值作为整体的值。
条件运算符:" ? : " 唯一三目运算符
(1)运算符作用:用于表达式或者条件操作的2选1
(2)运算符格式:条件表达式 ? 表达式1 : 表达式2;
(3)运算符操作原则:
① 首先计算条件表达式的值,判断真假
② 如果为真:执行表达式1,并将其结果作为整体的值
③ 如果为假:执行表达式2,并将其结果作为整体的值
到后期:三目运算符优于条件选择语句
数据类型转换
数据类型转换的方式有两种:自动类型转换、强制类型转换
(1)自动数据类型转换
① 含义:又称为隐式类型转化,自动按照从低到高的顺序转换(编译器自动转换)。Char +short ->int 特例: char和short类型的数据参与运算的时候,会自动转换成int类型。
(2)强制类型转换
① 含义:又被称为显式类型转换,是指原本数据类型不会发生变化,强制让其数据类型按照要求进行转换。
c
int main()
{
int A =1;
int B =10;
char C = (char)A+ (char)B;
float Num =1.23456789;
printf("%d\n",(int)Num); //显示类型转换
return 0;
}
数据类型转换原则
(1)char向int、float转换
① char向int转换,直接根据ASCII表转换成相应的整数;
② char向float转换,整数部分根据ASCII表转换成相应的整数,小数部分由0补齐。
(2)int向char、float转换
③ int向char转换:使用int数据和256求余,将结果赋值给char型变量(赋值给unsigned char,int%256+0)
④ int向float转换:保留整数位,小数位由0补足
(3)float向char、int转换
⑤ float向char转换:先擦除小数位,再用整数位对256求余
⑥ float向int转换:保留整数位,擦除小数位。
C语言基本结构
顺序结构 : C 语言中最基本的结构,是指程序按照从上到下、从左到右的顺序执行。需要注意的是,如果上一条语句没有执行完,后边的语句都不会被执行;
选择结构 :是指对于给定的条件进行判断,根据判断的结果来控制程序的执行过程。
循环结构 :是因为在程序中某些功能需要反复使用而设置的一种程序结构。循环结构常有两种模型来表示:一种是不达
目的不罢休,只要达到想要的结果(只关心结果,不关心循环次数),就停止循环;另外一种是不达次数不罢休,只要达到循环的次数(只关心循环次数,不关心循环结果),就停止循环。
九大语句
C 语言的核心是数据和算法,数据指数据类型和数值;算法包括运算符和 9 条控制语句。9 条控制语句在程序中主要实现一定的控制功能和逻辑功能。
选择语句
if语句
语句结构:if(如果)、else(否则、其他)、else if(其他如果)。
c
#include <stdio.h>
int main()
{
int inputNum;
printf("请输入一个数值:\n");
scanf("%d",&inputNum);
if(inputNum > 0 && inputNum < 10)
printf("在0与10范围内\n");
else if(inputNum >= 10 && inputNum < 20)
printf("在10与20范围内\n");
else if(inputNum >= 20 && inputNum < 30)
printf("在20与30范围内\n");
else if(inputNum >= 30 ) //如果成立就执行该if后的{}内的执行语句,以后的if不再判断执行,直接跳出这个结构
printf("在30以上范围内\n");
else if(inputNum >= 30 && inputNum < 40)
printf("在30与40范围内\n");
else
printf("不在范围内\n");
return 0;
}
switch语句
语句结构:switch(开关)、case(情况)、break(打断、停止)、default(默认)。
c
#include <stdio.h>
int main()
{
//输入1个数
int inputNum1;
printf("请输入1个数值:\n");
scanf("%d",&inputNum1);
switch(inputNum1)
{
case 0:printf("数值为0\n");break;
case 1:printf("数值为1\n");break;
case 2:printf("数值为2\n");break;
case 3:printf("数值为3\n");break;
default :printf("数值非0-3范围\n");break;
}
return 0;
}
循环语句
while语句
语句结构:while(当...的时候)。
c
#include <stdio.h>
int main()
{
int num = 10;
while(num)//循环打印9~0
{
printf("数据%d\n",--num);
}
return 0;
}
do while语句
语句结构:do(做)、while(当...的时候)。
c
#include <stdio.h>
int main()
{
int num = 9;
int data = 0;
do{
printf("%d\n",num);
} while(data++,num--); //num--后置自减 先引用后减1
printf("data:%d\n",data);
return 0;
}
while和do-while的区别:
1) 语句运行过程: while先判断后执行,do-while先执行后判断
2) 语句运行次数: while可能会不执行;do-while至少会执行一次。
for循环
语句结构:for( ; ; )。
c
#include <stdio.h>
int main()
{
//C89不支持中间定义变量
int i ;
for(i = 0; i < 10 ;i++)
{
printf("%d\n",i);
}
//C99支持中间定义变量
for(int i = 0; i < 10 ;i++)
{
printf("%d\n",i);
}
return 0;
}
转移语句
break
语句结构:break,放在循环语句体内,用于提前终止当前的循环操作。
c
#include <stdio.h>
int main()
{
//注:一个break只能结束一个循环语句,break会结束靠近它的循环语句(就近原则)。break不能结束if语句,但可以使用
if来控制break的发生,实现次数和结果的双重控制。
for(int i = 0;i <= 10;i++)
{
printf("i:%d\n",i);
if(i == 5 )
{
break;
}
}
return 0;
}
contiune语句
语句关键字:continue,放在循环语句体内,用于提前中止结束本次的循环操作。
c
#include <stdio.h>
int main()
{
for(int i = 0;i <= 10;i++)
{
if(i == 5) //跳过I=5
{
continue;
}
printf("i:%d\n",i);
}
return 0;
}
return语句
语句结构:return,放在函数体内,用于结束函数的运行(如果是在主函数的内部,则就会结束整个程序)。
c
#include <stdio.h>
int main()
{
for(int i = 0;i <= 10;i++)
{
if(i == 5)
{
return 1;
}
printf("i:%d\n",i);
}
printf("return前\n");
return 0;
}
goto语句
语句结构:goto XXX;,主要用于让程序无条件跳转到指定的标签位置,然后开始往下执行语句。
c
#include <stdio.h>
int main()
{
for(int i = 0;i <= 10;i++)
{
if(i == 5)
{
goto P;
}
printf("i:%d\n",i);
}
printf("P1前\n");
P:
printf("return前\n");
return 0;
}
语句应用:
① 往下跳转:忽略某些语句,让某些语句不执行,类似选择结构的效果,常用于快速的跳出多层循环体。
② 往上跳转:让某些语句重复执行多次,类似循环结构的效果,常用于模拟循环结构。
切记要谨慎使用goto,一个良好的健壮的程序一般不会使用goto语句。
函数
概念 :是指完成某种特定功能的程序代码。
执行顺序 :根据函数的主次顺序,可以分为主函数(唯一main函数)和子函数(其它)。
分类 :根据是否由自己编写,分为库函数 (系统调用函数、标准库函数、第三方库函数)和自定义函数 。
特性 :
模块化 :一个功能封装成一个函数,可以很方便的将这个功能移植到其他程序中。
独立性 :程序当中的每个函数彼此之间都是独立的。
通用性:一个函数不仅是为了解决一个问题,而是为了解决一类相似的问题,函数必须具有通用性。
下面举几个栗子:
1.字符串对比函数(strcmp)
c
#include<stdio.h>
#include<string.h>
int main()
{
printf("*******************strcmp********************************\n");
printf("%d\n",strcmp("abcd","abce")); //<0
printf("%d\n",strcmp("abcd","abcd")); //=0
printf("%d\n",strcmp("abce","abcd")); //>0
printf("*******************strncmp********************************\n");
printf("%d\n",strncmp("abcd","abcdef",6)); //<0 \0 -'e'
printf("%d\n",strncmp("abcd","abcdef",4)); //=0
printf("%d\n",strncmp("abcdf","abcdef",5)); //>0
return 0;
}
2.计算绝对值函数
c
#include <stdio.h>
#include <math.h>
int main()
{
int num = -2;
//原始写法
if(num < 0) num = -num;
printf("%d\n",num);
//函数写法
printf("%d\n",abs(num));
return 0;
}
自定义函数
举个栗子把:
第一种:
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int add(int num1,int num2); //声明函数的返回值类型为int 函数的形参有2个都为int
int main()
{
printf("%d\n",add(1,2)); //调用函数
return 0;
}
int add(int num1,int num2)
{
return num1+num2;
}
第二种
c
#include <stdio.h>
#include <math.h>
int add(int num1,int num2) //声明函数的返回值类型为int 函数的形参有2个都为int
{
return num1+num2;
}
int main()
{
int a = 1, b = 2;
printf("%d\n",add(a,b)); //调用函数,传参可以是变量的名字
return 0;
}
要注意的点:
1)当被调用的函数在函数调用之前(函数定义在函数调用之前),可以省略函数声明。但是,当被调用的函数在函数调用之后(函数定义在函数调用之后),则必须进行函数声明。
2)一个函数如果有类型,那么这个函数就必须有返回值 。定义函数的类型就是为了定义函数返回值的类型,函数类型和函数返回值的类型必须保持一致,如果不一致,返回值的类型会强制向函数数据类型转换。
3)函数如果有形参,那么函数调用的时候,必须有实参向形参传递。定义形参的类型、顺序就是为了定义实参的类型、顺序(如果不一致,实参的数据类型会强制向形参的数据类型转换),函数形参为空,则不需要实参传递。
变量和函数的作用范围
全局函数 是指这个函数对整个工程可见,在整个工程的任意一个地方都可以被调用。在函数类型之前加上关键字"extern(外部的)",则表示声明该函数为全局函数。
局部函数 是指这个函数只能被当前所在的程序源文件所使用,工程中的其他源程序文件是无法对这个函数进行调用。在函数类型之前加上关键字"static(固定的、静态的)",则表示声明该函数为局部函数。
全局变量 是指在函数体外面定义的变量即为全局变量,变量的初始值为0。
局部变量是指在函数体内部定义的变量,变量的初始化为随机数(垃圾值)。局部变量的作用时间是从变量定义的位置(函数调用)开始,到所在的程序运行结束。局部变量的作用范围只有在定义的函数内有效。
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int A = 99; //全局变量
void function()
{
int A =88; //局部变量
printf("A:%d\n",A); //就近原则 局部有效
}
int main()
{
function();
return 0;
}
静态变量是局部变量的一个特殊例子,在局部变量的数据类型之前加上关键字"static",则可以把局部变量变成一个静态变量。静态变量有着全局变量的生命周期(作用时间),局部变量的作用域(作用范围)。在函数内部定义,一旦初始化,在程序运行的过程中,一直存在,程序即使跳出了该函数,也不会被释放,在下次调用时不会被重新初始化。
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
void function()
{
//int A =88; 每次函数运行时都会运行
//静态变量 static int A =88; 只运行一次 后期不再运行
static int A =88; //开机运行初始化88次
A++;
printf("A:%d\n",A);
}
int main()
{
function();
function();
function();
function();
function();
return 0;
}
内存五大区
堆区
堆区也称动态内存分配区 。堆区的空间程序在运行的时候,由程序员使用 "malloc"函数 去申请的内存空间,空间一旦申请完成,需要程序员使用 "free"函数 去释放内存,即使申请后没有进行释放,到整个工程结束后自动释放 。
作用时长 :malloc创建成功------>free释放成功时结束/整个工程结束后自动释放;
使用场景 :动态申请内存,以数组为例:char buff[100]长度是受到限制,如果采用malloc/calloc等等就可以灵活的创建/修改长度,节省内存避免空间浪费;
注意点 : 如果一直申请却没有释放则会导致内存泄漏
栈区
在执行函数时,函数(包括main函数)内局部变量以及函数的形参的存储单元空间都是在栈区中创建,栈区中的变量控制由编译器自动开辟,函数运行结束之后自动释放 。
作用时长 : 创建生效->在函数运行结束;
使用场景 :函数调用、定义变量(去除static修饰的情况)、函数的形参传递
注意点: 变量使用栈区的初值一定要规定,如果不规定出现垃圾值后果不堪想象;
全局变量区
全局变量区也称为全局/静态区 ,主要是存放全局变量和静态变量。全局变量区中的变量由编译器自动开辟,当整个工程结束之后自动释放 。
作用时长 :全局变量/静态变量创建成功->整个工程结束后自动释放;
使用场景 : 多个源文件/函数中使用该变量/数组可以考虑使用全局变量;对于函数中某些变量需要进行记忆的话可以考虑使用静态变量;
注意点 : 尽量少使用全局变量,因为安全性不高
文本常量区
常量区是一块比较特殊的数据存储区,在程序运行的过程中,只允许程序去访问,不允许程序进行修改(只读) 。常量区主要存放的是文字常量。
作用时长 :工程开始->工程结束
使用场景 : 常用字符串/字符/常量;
注意点 : 只能读取内容不能写入内容;
代码区
程序代码区主要是用于存放二进制代码,只能读取不能修改。
数据结构
数组
数组就是多个具有相同数据类型的数值有序的集合在一起。根据存储的方式,又分为"一维数组"和"二维数组";那根据存储的内容,又分为"数值数组"、"字符数组"以及"字符串数组"。
数组的大小计算公式为 数组长度 X 单个字符的大小(字节)
数组的特点
空间地址:数组的元素在存储器中是连续的 (存储地址是连续的->存储空间是一整块)。
访问数据:通过一个数组名,就可以访问数组中所有的元素。数组名+成员运算符即可访问buff[1]
当程序中出现了大量相同类型(含义)的数据时,就可以考虑使用数组。但在使用数组的时候,重点要关注数组中元素的个数(数组的长度),否则就会出现空间不足和空间浪费的问题。
长度太小 会导致空间不足,进而发生内存访问越界;
长度太长则会导致空间浪费内存不足。
一维数组的定义、赋值与访问
举个栗子:在键盘上输入任意10个自然数,求自然数之和
c
#include <stdio.h>
int main()
{
//1在键盘上输入任意10个自然数,求自然数之和
unsigned int buff[10]={0};//初始化防止数组的数据存在脏数据
unsigned int sum =0;
for(int i = 0;i < 10; i++)
{
printf("请输入%d个自然数\n",i+1);
scanf("%d",&buff[i]);
}
for(int i = 0 ;i < 10 ; i++)
{
sum +=buff[i];
}
printf("sum:%u\n",sum);
return 0;
}
再举个栗子:字符串的获取
首先来个错误的栗子:
c
#include <stdio.h>
int main()
{
char buff[100]={0};
//buff[0]="hello"; "hello"提供的是首地址,buff[0]存放的为一个字符
buff[0]="hello";//错误写法 不要犯
printf("%s\n",buff);
return 0;
}
接下来这个栗子是用scanf和gets两种方法获取字符串
c
#include<stdio.h>
#include<string.h>
int main()
{
int index = 0;
char inputString[100]={0};
printf("请输入一串字符串\n");
gets(inputString);
while(inputString[index] != '\0') // \0 是字符串终止符号
{
index++;
}
printf("%s\n",inputString);
printf("请再输入一串字符串\n");
char inputstring2[100]={0};
scanf("%s",inputstring2);
printf("%s\n",inputstring2);
return 0;
}
scanf:只能捕获空格、tab键、回车之前的字符
gets:只能捕获回车之前的,可以包含TAB、空格这些字符
二维数组的定义、赋值与访问
c
#include <stdio.h>
int main()
{
int buff[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}}; //全赋值
// int buff[3][4]={ 1,2,3,4, 2,3,4,5, 3,4,5,6}; //全赋值
// int buff[3][4]={ 1,2,3,4, 2,3,4,5}; //部分赋值
// int buff[][4]= {{1,2,3,4},{5},{6,7,8,9}}; //省略行长度
// int buff[][4]={1,2,3,4,5,6,7,8,9}; //省略行长度
//切记只能省略行长度不然所有的数据都会在一行上
for(int i = 0;i < 3; i++)
{
for(int j = 0; j < 4; j++)
{
printf("%d\t",buff[i][j]);
}
printf("\n");
}
return 0;
}
结构体
结构体是一种构造数据类型,使用关键字"struct "来表示。是指一系列相同数据类型或者不同数据类型的数据构成的集合 。结构体中的成员变量跟数组中的元素一样,在存储空间中都是连续存储的。
结构体的定义与使用
c
#include<stdio.h>
#include<string.h>
//struct->表示这是一个结构体类型,即我们在构造并且声明一种结构体类型 C11版本
//Person->自定义标识符,命名规则同变量。建议使用突出类型意义的词汇。
//{}作用域->结构体内不能出现重名问题
struct Person{
int W; //数据在结构体中叫做成员变量
int H; //成员变量
int Age; //成员变量之间类型可以相同也可以不同
char Name[30]; //基本数据类型,也可以是构造数据类型
void (*Action)(void); //行为
};//末尾的分号不能省略,这个分号表示结构体类型声明结束。
//对于结构体所放位置: 结构体类型定义在全局的位置,让所有的函数都可以直接调用。一般是放在.h中
//由于只是构造与声明所以不能进行赋值
//不管是哪种编译器 都用最通用原则 先进行声明,再进行定义
struct Person a; //全局变量
int main()
{
struct Person b; //栈区变量
printf("%d\n",a.W);
printf("%d\n",b.W);
return 0;
}
上面的栗子是先声明结构体类型,再使用声明好的结构体类型去定义结构体变量。接下来我们看个 在声明结构体类型的同时定义结构体变量的栗子:
c
#include<stdio.h>
#include<string.h>
struct Person{
int W;
int H;
int Age;
char Name[30];
void (*Action)(void);
}xiaoming,xiaohua; //在声明结构体类型的同时定义结构体变量->全局变量xiaoming xiaohua
int main()
{
printf("%d\n",xiaoming.W);
printf("%d\n",xiaoming.W);
return 0;
}
接下来我们在看一个在 声明结构体类型的同时,定义结构体变量,结构体类型名可以不写,但是这种结构体类型只能在声明的同时定义变量,往后不能再用此结构体类型单独去定义其他变量的栗子:
c
#include<stdio.h>
#include<string.h>
//利用类型重定义去重新定义结构体类型的名称,然后利用新的结构体类型名称定义结构体变量。
//类型重定义关键字:typedef
typedef struct person{
int W;
int H;
int Age;
char Name[30];
void (*Action)(void);
}PERSON;
//typedef -> 将struct person类型重命名为PERSON,这样子原名/现名都能使用 避免了类型冲突
PERSON a;
struct person b;
//忽略结构体类型名,匿名类型重命名问题:
typedef struct{
int W;
int H;
int Age;
char Name[30];
void (*Action)(void);
}PERSON1; //typedef 将匿名类型重命名为PERSON1
//结构体类型名与重命名名出现冲突问题:
typedef struct person1{
int W;
int H;
int Age;
char Name[30];
void (*Action)(void);
}person1;
//typedef 将struct person1类型重命名为person1 可以实现但是多此一举;
int main()
{
return 0;
}
结构体变量的访问
- 通过结构体变量名访问: 结构体变量名.成员变量名
- 通过指针访问: 指针名->成员变量名,(*指针名).成员变量名
c
#include <stdio.h>
#include <string.h>
typedef struct person{
int W;
int H;
int Age;
char Name[30];
void (*Action)(void);
}PERSON;
int main()
{
//1.1 通过结构体变量名访问: 结构体变量名.成员变量名
PERSON a;
a.W =100;a.H =170; a.Age = 18;strcpy(a.Name,"小明");
printf("%s 体重:%d 身高:%d 年龄:%d\n",a.Name,a.W,a.H,a.Age);
//1.2 通过结构体变量名访问: 结构体变量名.成员变量名
struct person b; //C99
b.W =100;b.H =170;b.Age = 18;strcpy(b.Name,"小明");
printf("%s 体重:%d 身高:%d 年龄:%d\n",b.Name,b.W,b.H,b.Age);
//2 通过指针访问: 指针名->成员变量名,(*指针名).成员变量名
PERSON *pa = &a;
pa->W =100;pa->H =170;pa->Age = 19;strcpy(pa->Name,"小明");
printf("%s %d %d %d\n",pa->Name,pa->Age,pa->W,pa->H);
printf("%s %d %d %d\n",(*pa).Name,(*pa).Age,(*pa).W,(*pa).H);
return 0;
}
(1)"."的优先级高于"*",取内容的时候一定要加(),一般在结构体中使用"->"来访问结构体指针变量中的数据。"."一般用于结构体变量,"->"只适用于结构体指针。
(2)在声明结构体类型的同时,定义结构体变量,结构体类型名可以不写,但是这种结构体类型只能在声明的同时定义变量,往后不能再用此结构体类型单独去定义其他变量。
结构体的赋值
初始化赋值、逐一赋值、整体赋值;
c
#include <stdio.h>
#include <string.h>
typedef struct person{
int W;
int H;
int Age;
char Name[30];
void (*action)(void);
}PERSON;
void emotion(void)
{
printf("充满爱\n");
}
int main()
{
//1.1初始化全赋值
PERSON xiaoming={100,170,18,"小明",emotion};//初始化赋值内容顺序一定要和声明时成员的顺序保持一致
printf("%s %d %d %d\n",xiaoming.Name,xiaoming.W,xiaoming.H,xiaoming.Age);
xiaoming.action();
//1.2初始化部分赋值---未赋值的成员默认为0
PERSON xiaohua={50}; //NULL 0
printf("%s %d %d %d\n",xiaohua.Name,xiaohua.W,xiaohua.H,xiaohua.Age);
//2逐一赋值
Person a;
a.W = 100;a.H = 170;a.Age = 18;a.action = emotion;
strcpy(a.Name,"小明");//数组不能整体赋值
//3整体赋值 结构体 同类型之间可以进行整体赋值
Person b;
b = a;
return 0;
}
结构体嵌套
结构体嵌套是指一个结构体类型内嵌套另外一个结构体类型,在访问被嵌套的结构体的成员变量之前,需要先访问被嵌套的结构体变量名,再访问被嵌套的结构体的成员变量。
在结构体嵌套时需要注意被嵌套的结构体成员变量的结构体类型,一定要在嵌套结构体数据的成员变量的结构体类型之前声明。
c
#include <stdio.h>
#include <string.h>
typedef struct {
int precedence;
float money;
}Honor; //荣誉
typedef struct {
int W;
int H;
int Age;
char Name[30];
Honor status; //前提先要有该类型
void (*action)(void);
}Person;
void emotion()
{
printf("充满爱\n");
}
int main()
{
Person a={100,170,18,"小明",{1,10000.00},emotion};
printf("名字:%s\n",a.Name);
printf("年龄:%d\n",a.Age);
printf("体重:%d\n",a.W);
printf("身高:%d\n",a.H);
//先访问被嵌套的结构体变量名,再访问被嵌套的结构体的成员变量
printf("位次:%d\n",a.status.precedence);
printf("财富:%lf\n",a.status.money);
a.action();
return 0;
}
结构体数组
结构体数组本质上是一个数组,数组中元素的类型是结构体类型。使用"结构体关键字 结构体类型 变量名[数组长度]"的格式来定义一个结构体数组。例:struct STD x[10];
c
#include <stdio.h>
#include <string.h>
struct sex{
unsigned int sexFlage:1;//性别只有两种 0 1
};
typedef struct{
intID; //学号
int Age; //年龄
char Name[30]; //名字
struct sex Sex; //性别
}STU;
int main()
{
//学生结构体数组
STU stu[100]; //最多存放100学生信息
stu[0].ID =1;stu[0].Age =18;strcpy(stu[0].Name,"小明");stu[0].Sex.sexFlage =1;
printf("%s %d %d %s\n",stu[0].Name,stu[0].ID,stu[0].Age,stu[0].Sex.sexFlage?"男":"女");
stu[1].ID =2;stu[1].Age =19;strcpy(stu[1].Name,"小小");stu[1].Sex.sexFlage =0;
printf("%s %d %d %s\n",stu[1].Name,stu[1].ID,stu[1].Age,stu[1].Sex.sexFlage?"男":"女");
return 0;
}
结构体大小的计算
计算结构体类型空间大小的原则:结构体的有效对齐值
1) 数据对齐原则---内存按结构体成员的先后顺序排列,当排到该成员时,其前面已开辟的空间字节数必须是该成员类型所占字节数的整数倍,如果不够则补齐,依次向后类推。
2) 整体空间是占用空间最大的成员类型所占字节数的整数倍(系统对齐方式和结构体自身默认对齐方式,俩者取最小的)。
例如32位操作系统:对齐方式按4字节对齐;
例如64位操作系统:对齐方式按8字节进行对齐;
c
typedef struct{
char a;
double d;
}StructSizeof;
比如这个结构体32位新系统下就是 4 + 8 = 12 字节,但是64位系统下就是 4 + 8 = 12 -> 16 字节。他会自动的去对齐最小存储单位。
对齐指令
用这个指令指定的对齐方式的时候,会与结构体自身默认对齐方式比较,俩者取最小的。
c
//N=1的时候 不对齐
#include <stdio.h>
#include <string.h>
#pragma pack(1)
typedef struct{
char a; //1
double d; //8
}StructSizeof;
int main()
{
printf("%d\n",sizeof(StructSizeof)); //9
return 0;
}
c
//N=3的时候 3字节对齐
#include <stdio.h>
#include <string.h>
#pragma pack(3)
typedef struct{
char a; //1 补空2字节
double d; //8 double分为3块 每块3字节 后补1字节
}StructSizeof;
int main()
{
printf("%d\n",sizeof(StructSizeof)); //12 警告提示不能是3
return 0;
}
共用体
共用体是一个特殊的结构体,使用关键字"union"来表示一个共用体。共用体中的数据成员共用一个内存空间,成员变量的最新赋值会把之前的数据覆盖掉。共用体变量在存储器中占用的存储空间就是共用体最大成员变量占用的空间,并且是最大成员变量的数据类型所占空间的整数倍。
c
#include <stdio.h>
#include <string.h>
//对于共用体 占用一块内存空间
union Mem{
char data;
short data0;
int data1;
float data2;
double data3;
};
int main()
{
printf("%d\n",sizeof(union Mem));
return 0;
}
- 同一内存段可以用来存放几种不同类型的成员,但是每次只能存放其中一种,而不能同时存放所有的类型。也就是说在共用体中,只有一个成员起作用,其他成员不起作用
- 共用体变量中起作用的成员是最后一次存放的成员。在存入一个新成员后,原有的成员就失去作用
- 共用体变量的地址和它的各成员的地址是一样的
- 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
枚举
枚举类型实际上是一组整型常量,使用关键字"enum"来表示一个枚举类型。
c
#include <stdio.h>
#include <string.h>
enum TIMBUFF{TIM0=10,TIM1=99,TIM2,TIM3,TIM4};
//原来
void oldStart(int num)
{
switch(num)
{
case 10: printf("定时器0\n");break;
case 99: printf("定时器1\n");break;
case 100: printf("定时器2\n");break;
}
}
//代码可读性增强
void newStart(enum TIMBUFF num)
{
switch(num)
{
case TIM0: printf("定时器0\n");break;
case TIM1: printf("定时器1\n");break;
case TIM2: printf("定时器2\n");break;
}
}
int main()
{
oldStart(1);
//底层源码才能获知1 2 意思
newStart(TIM1);
printf("TIM2:%d\n",TIM2);
return 0;
}
宏定义与应用
宏定义就是替换的意思,主要是把表达式、常量等内容,利用一个自定义的标识符号进行替换。
c
//1 给一个数据类型起别名
#define DATA_INT int
//2 替换一个表达式
#define A 1+2
//3 替换一个运算公式
#define LEN(x) strlen(x)
条件编译
一般用于防止头文件的重复编译
c
#include <stdio.h>
#include <string.h>
#define _INIT_ //条件编译标识与变量名是不一致的,需要区别
//:如果标识符已经被定义,执行程序段1,未定义,执行程序段2。
int main()
{
#ifdef _INIT_
int A =10;
int B =20;
int C = A+B;
printf("int C = A+B:%d\n",C);
#else
int A =20;
int B =30;
int C =B-A;
printf("int C =B-A:%d\n",C);
#endif
return 0;
}
存储类型
①auto在C语言中只有一个作用就是修饰局部变量,表示这个变量是自动变量,分配在栈上,平时定义的局部变量就是自动局部变量,只是省略了auto。
c
int A =10;
auto int A =10;
②static
第一种就是修饰局部变量:static修饰的局部变量只是改变了存储类型,其存储方式和全局变量一样,作用域和普通局部变量一样。
第二种就是修饰全局变量和函数:static修饰全局变量和函数只是改变了他们的链接属性由外链接变为内链接(.C文件内),全局变量和函数默认的链接属性为外链接(可以跨文件进行链接)。
③register这个关键字不常用,register修饰的变量,编译器会尽量将他们分配在寄存器中,(平时分配的一般变量都是在内存中,分配在寄存器中一样的用,但是读写效率会提高很多)。所以register修饰的变量通常是被反复高频率的使用,通过改善这个变量的访问效率可以极大的提升程序的运行效率。
c
ttps://blog.csdn.net/21aspnet/article/details/257511
④volatile (可变的 易变的) C语言中volatile用来修饰一个变量,表明这个变量可以被编译器之外的东西改变(编译器不可预知的东西,比如硬件自动更改了这个变量的值,一般这个变量是一个寄存器的值,比如在多线程中在别的线程更改了这个变量的值),如果不该加时加了会降低效率。
c
https://www.cnblogs.com/hjh-666/p/11148119.html
其他类型
①const:声明一个变量的值不可变,要求const修饰的变量在定义时必须进行初始化。
c
const int A; A =10;//错误的
const int A=10; //初始化时进行赋值 正确的
return 0;
入门部分
恭喜你真正的入门了,接下来你就会看到一些奇奇怪怪的有意思的东西了 OVO
指针
指针介绍
计算机中的存储地址是以字节为单位的一片连续的存储空间,每一块空间都由自己唯一的一个地址编号(非负整数,从1开始自然增长),也叫字节编址。计算机中使用16进制来表示地址编号。
变量名 :在定义变量后,编译器会在内存中开辟一个空间,变量名就是这个变量所占用内存单元的名字。
变量内容 (变量值):就是变量所占内存单元中所表示的数据。可以对变量进行读写操作,读变量就是从这个空间中将变量的值取出来,写变量就是把数值写入到这个变量所占用的内存单元中。
变量地址 : C 程序的变量都有自己的数据类型,编译器根据数据类型去开辟对应字节大小的字节空间。所谓的变量地址就是这个字节空间的首地址。
总结: 对于变量地址及变量名都能够确定唯一内存地址空间;对于变量内容不能确定唯一的内存地址空间; 能够确定唯一的内存地址空间的标识/地址,操作者即可以对该空间进行修改/读取内容操作;
指针也是一种数据类型,32位系统下指针大小位4个字节,8位系统下指针大小位3个字节,64位系统下指针大小位8个字节。 指针大小与数据类型无关,它与系统位数有关。指针变量通常用来保存地址,通过指针可以访问保存在地址中的数据。
常量与指针
常量指针 指向常量的指针 ,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量。
c
const int *P =&XXX; //const 修饰类型
int const *P =&XXX;
指针常量 指针本身是常量 。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。它指向的地址将伴其一生,直到生命周期结束。有一点需要注意的是,指针常量在定义时必须同时赋初值。
c
int *const P =&XXX; //const 修饰指针
常量指针常量 顾名思义,指针本身和指向的内容都不可以被修改
c
const int *const P =&XXX;
指针应用
指针的定义与初始化
c
#include <stdio.h>
int main()
{
int *PA ; //A在栈区 野指针
printf("%u\n",sizeof(PA));
printf("%p\n",PA); //栈区特点:变量值为随机值(垃圾值)
//此时赋值 *A = 10; 是非法访问,因为没有给A申请空间也就是初始化
//段错误 就是地址访问错误 在编译器变量表中没有该地值的空间开辟;
//以上A为野指针
int B =10;
int *PB =&B;//对于PB指针进行赋值为B空间首地址
printf("B:%p\t PB:%p\t*PB:%d\n",&B,PB,*PB);
int *PC =NULL;//将PC指针指向为空 NULL==(void*)0; 避免野指针
//系统中0-4K只能读不能写;
printf("PC:%p\n",PC);
return 0;
}
指针变量和普通变量之间的关系
(1) 指针变量用来指向(存储)普通变量的地址。
(2) 可以通过指针变量来操作普通变量空间中的值。
(3) 一个变量可以有多个指针变量同时指向,但是一个指针变量每次只能指向一个普通变量 。
(4)指针:大小为4字节空间(只能存储一个地址)不能存储多个地址;
(5) 指针变量指向的地址是可变的变量,普通变量的地址是一个常量。
(6)普通变量可以使用C语言运算符进行运算,但指针变量只能进行+、-、++、--运算,不能做*、/、%运
算。
指向数组的指针
指针和数组一样,都可以通过变量的下标来访问数组中的各个元素。指针变量+1,指针内存放的地址+n(n
由指针指向空间类型决定)。使用"++"运算符来访问数组中的下一个元素,使用"--"运算符来访问数组中的
上一个元素。
c
#include <stdio.h>
//对于连续的空间内容,尽量采用传地址方式解决
void function(int * buff_p)
{
for(int i =0;i < 10; i++)
{
//printf("%d\n",buff_p[i]); //将指针隐含转为数组名,通过下标访问
printf("%d\n",*buff_p++); //通过指针偏移访问++
}
}
int main()
{
int inputNumBuff[10]={1,2,3,4,5,6,7,8,9,10};
function(inputNumBuff);
return 0;
}
指向字符串的指针
指向字符串指针和字符串数组的区别
1) 指针是一个地址变量,可以指向不同的字符串(指向不同的地址)。
2) 数组是一个地址常量,只能用固定的地址来保存字符串(地址是个常量)。
c
char *p_str = "hello";
指向指针的指针
指向指针的指针也称为二级指针,一般是配置指针数组使用。一般情况下很少使用,使用的时候慎用。使用"指针类型 *变量名"的格式来定义一个二级指针。例如:int ** p。
c
#include <stdio.h>
int main()
{
int A =10;
int *PA =&A;
int ** PPA= &PA;
printf("&A:%p\tA:%d\n",&A,A);
printf("&PA:%p\tPA:%p\n",&PA,PA);
printf("PPA:%p\t*PPA:%p\t**PPA:%d\n",PPA,*PPA,**PPA);
return 0;
}
指针数组
指针数组本质上是一个数组(指针类型的数组),数组中的每一个元素的类型都是指针类型,使用的时候结合数组和指针规则使用。
c
#include <stdio.h>
#include <string.h>
int main()
{
int A =10;
int B =20;
int C =30;
int D =40;
int E =50;
//定义指针数组
int * numP[10]={&A,&B,&C,&D,&E,NULL,NULL,NULL,NULL,NULL};
printf("%d\t%d\t%d\t%d\t%d\n",*numP[0],*numP[1],*numP[2],*numP[3],*numP[4]);
//存放文本常量区中的字符串首地址
char * stringBuff[] = {"1234","2345","3456","4567","5678"};
printf("%s\n",stringBuff[2]);
return 0;
}
数组指针
数组指针的本质是一个指针(数组类型的指针),数组指针常用于访问二维数组时使用。使用"数据类型(*变量名)[数组长度]"的格式来定义一个数组指针。
c
#include <stdio.h>
#include <string.h>
int main()
{
int baseArr[3][4]={
{1,2,3,4},
{2,3,4,5},
{3,4,5,6}
};
int (*p)[4] = baseArr;
//数据
printf("baseArr[0][0]:%d\n",baseArr[0][0]);
printf("baseArr[1][0]:%d\n",baseArr[1][0]);
printf("baseArr[2][0]:%d\n",baseArr[2][0]);
//地址
printf("&baseArr[0][0]:%p\n",&baseArr[0][0]);
printf("&baseArr[1][0]:%p\n",&baseArr[1][0]);
printf("&baseArr[2][0]:%p\n",&baseArr[2][0]);
//数组指针 访问行地址 数组指针又称之为 行指针 p==
printf("p:%p\n", p);
printf("p+1:%p\n", p +1);
printf("p+2:%p\n", p +2);
//内容 获取每一行首个元素内容
printf("%d\n",**p);
printf("%d\n",**(p+1));
printf("%d\n",**(p+2));
return 0;
}
结构体指针
终于能补齐前面结构体缺少的部分了 嘿嘿 0.o?
结构体指针本质上是一个指针,指针的类型是结构体类型。使用"结构体关键字 结构体类型 *变量名"的格式来定义一个结构体指针。
c
#include <stdio.h>
#include <string.h>
typedef struct{
int ID; //学号
int Age; //年龄
char Name[30]; //名字
}STU;
int main()
{
//学生结构体数组
STU stu[100]; //最多存放100学生信息
stu[0].ID =1;
stu[0].Age =18;
strcpy(stu[0].Name,"小明");
printf("%s %d %d %s\n",stu[0].Name,stu[0].ID,stu[0].Age);
//结构体指针
STU *stup = &stu[0];//stu
printf("%s %d %d %s\n",stup->Name,stup->ID,stup->Age);
return 0;
}
指针函数
指针函数的本质上是一个函数(函数的类型是指针类型 ),函数的返回值(如果是局部变量,释放会导致程序崩溃,该变量必须是静态局部变量,返回值是该静态局部变量的地址)也是一个指针或数组首地址。用
法和函数一样,常用于返回数组以及指针变量。
函数类型:指针类型 int * fun(void) 该函数返回一个地址;
地址的返回只能返回静态变量的首地址、动态开辟(malloc)的地址,全局变量的地址、指针地址等,不能返回局部变量的地址。
c
#include <stdio.h>
#include <string.h>
char * mystringcat(char * str1,char *str2)
{
int len = strlen(str1);
int len2 = strlen(str2);
strcpy(str1+len,str2);
return str1;
}
int main()
{
char buff[100]="hello";
char buff2[100]="123456";
printf("%s\n",mystringcat(buff,buff2));
return 0;
}
函数指针
函数指针本质上是指针,指针指向的类型是一个函数类型,用法和指针一样。使用"函数类型 (*变量名)(形参列表) = (初始化内容)"的格式去定义一个函数指针。
c
//简易计算器
#include <stdio.h>
#include <string.h>
//加法
int add(int num1,int num2)
{
return num1+num2;
}
//减法
int sub(int num1,int num2)
{
return num1-num2;
}
//乘法
int mul(int num1,int num2)
{
return num1*num2;
}
//除法
int div(int num1,int num2)
{
return num1/num2;
}
int main()
{
//函数指针类型
int (*bc)(int,int) =NULL;
int data1,data2;
char op;
printf("data1 op data2\n");
scanf("%d%c%d",&data1,&op,&data2);
//通过switch语句实现计算器选择----函数指针方式
switch(op)
{
case '+': bc = add;break;
case '-': bc = sub;break;
case '*': bc = mul;break;
case '/': bc = div;break;
}
printf("结果:%d\n",bc(data1,data2));
return 0;
}
指针四要素
① 指针必须有类型,指针类型是声明指向对象的类型。
② 指针所指向的数据类型,决定了指针值(指针指向对象地址里的数值)的数据类型。
③ 指针在使用时必须要有指向,避免野指针出现。
④ 指针本身也占内存空间(指针本身也是一个变量)。
动态内存分配
动态申请的空间,函数返回值均为void *类型,即可以返回任意类型的地址,在使用时要强转成对应指针类型的地址。函数头文件:#include <stdlib.h>
malloc
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int main()
{
int * p = (int *)malloc(sizeof(int)*2); //malloc申请(int)*2大小的空间
//p指针类型中基本类型为int 每步步长为int(4字节) p+1 对开辟的空间偏移4字节
printf("开辟空间的首地址:%p\n",p);
if(p == NULL)
{
printf("开辟空间失败\n");
perror("malloc");//perror()函数打印str(字符串)和一个相应的执行定义的错误消息到全局变量errno中.
return 1;
}
else
{
*p = 10;
*(p+1) =11;
//输出空间中的内容
printf("%d\n",p[0]);
printf("%d\n",p[1]);
}
return 0;
}
calloc函数
c
int main()
{
int * p = (int *)calloc(2,sizeof(int)); //向存储器申请2块,每块大小为4字节的空间
//p指针类型中基本类型为int 每步步长为int(4字节) p+1 对开辟的空间偏移4字节
printf("开辟空间的首地址:%p\n",p);
if(p == NULL)
{
printf("开辟空间失败\n");
perror("malloc");//perror()函数打印str(字符串)和一个相应的执行定义的错误消息到全局变量errno中.
return 1;
}
else
{
*p = 10;
*(p+1) =11;
//输出空间中的内容
printf("%d\n",p[0]);
printf("%d\n",p[1]);
}
return 0;
}
relloc函数
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int main()
{
//int *buff = (void*)malloc(100*4); //开辟堆区的一块空间
//int *p = (void *)realloc(buff, 200);//void 与 int 会报错kee能使因为我用的C++编译器 C++是强类型语言,void* 不能隐式转换为其他指针类型
int *buff = (int *)malloc(100*4);
int *p = (int *)realloc(buff, 200);
printf("%p\n",p);
if(p == NULL)
{
perror("realloc");
}
return 0;
}
free函数
free函数只能释放最近分配的空间以及内存动态分配的空间,函数是从stdlib.h头文件中调用。在程序中,使用malloc函数和calloc函数申请的空间,在程序运行的过程中,程序不会主动释放空间,所在每次存储空间使用完毕后,一定要手动释放(使用free函数释放)。
c
int main()
{
char * desc =NULL;
char inputStringBuff1[100];
printf("请输入一串字符串\n");
gets(inputStringBuff1);
mystrcat(&desc,inputStringBuff1);
printf("%s\n",desc);
//使用完成以后应该释放空间
free(desc);
return 0;
}
链表
数据结构是指相互之间存在关系的多个数据元素的集合方式。常用的数据结构有顺序存储结构 (数组)和链式存储结构 。
1)顺序存储结构 是指数据元素按照顺序在内存中连续存放的 ,可以通过相对位置来表示数据元素之间的关系。
2)链式存储结构 是指元素在内存单元中不是连续存放的,前一个元素保存下一个元素的地址,通过地址来表示元素之间的关系。
链表是由一种特殊的结构体构成的,所包含的结构体个数可以有任意个(类似于结构体数组),但是链表在内存中是不连续存放的(区别于结构体数组)。链表可以合理的利用内存空间,分配内存资源。
链表的定义与使用
链表是由表头、若干个节点(链表的数据元素)以及表尾组成,每一个节点中包含着数据域(用来存放数据的地方)和指针域(用于存放其他节点的地址,下一个节点的首地址)两部分内容。并且一个完整的链表由表头、节点以及表尾三部分组成。

链表栗子
链表分为静态链表和动态链表,这里只举一个链表使用的栗子:
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//枚举
typedef enum{
false =0,
true =1
}bool;
/************************************链表结构体数据域的创建与声明****************************/
typedef struct{
int id;
char name[30];
int sex;
int age;
float chengji;
}Data;
/************************************链表结构体创建与声明************************************/
typedef struct LinkList{
//数据域
Data data;
//指针域
struct LinkList* nextNodeAddress;
}LINKLIST;
LINKLIST *headNodePtr =NULL; //链表全局的头指针
/************************************链表存储相关功能函数************************************/
/***********************************************************
函数功能:创建节点
函数名称:LINKLIST *createNewLinkListNode(Data data)
函数参数:链表结构的数据域---要存储的数据
返 回 值:创建的新节点地址
************************************************************/
LINKLIST *createNewLinkListNode(Data data)
{
LINKLIST * newP = (LINKLIST *)malloc(sizeof(LINKLIST)); //在堆区开辟一块大小为sizeof(LINKLIST)字节空间
if(newP == NULL)
{
printf("创建节点失败\n");
return NULL;
}
else
{
newP->data = data;
newP->nextNodeAddress = NULL;
return newP;
}
}
/***********************************************************
函数功能:追加添加节点
函数名称:bool appendLinkListNode(LINKLIST *headPtr,Data data)
函数参数:headPtr头节点的指针 data为数据域
返 回 值:成功true 不成功false
************************************************************/
bool appendLinkListNode(LINKLIST *headPtr,Data data)
{
//辅助指针 如果对指针进行偏移时需要使用辅助指针
LINKLIST *tempP = headPtr;
//进行遍历---找到哪个节点它的指针域为空 如果对应为空,需要将其赋于新节点地址
while(tempP->nextNodeAddress != NULL)
{
tempP = tempP->nextNodeAddress;
}
//创建新节点
LINKLIST * newNodePtr = createNewLinkListNode(data);
if(newNodePtr != NULL)
{
//将新节点地址赋值至表中指针域---进行穿串操作
tempP->nextNodeAddress = newNodePtr;
return true;
}
else
{
return false;
}
}
/***********************************************************
函数功能:指定位置添加节点
函数名称:bool insertSpecifyLocationLinkListNode(LINKLIST *headPtr,int nodeNum,Data data)
函数参数:nodeNum在第几个节点插入数据 data数据
返 回 值:成功true 不成功false
************************************************************/
bool insertSpecifyLocationLinkListNode(LINKLIST *headPtr,int nodeNum,Data data)
{
int currNodeNum =1; //记录节点个数
//辅助指针 如果对指针进行偏移时需要使用辅助指针
LINKLIST *tempP = headPtr->nextNodeAddress;
//进行遍历---到哪个节点它的指针域为空啊 如果对应为空,需要将其赋于新节点地址
while(tempP!= NULL)
{
currNodeNum++;
//检验tempP中数据域
if(currNodeNum == nodeNum)//直接插入到第nodeNum位置
{
//创建新节点
LINKLIST *newPtr = createNewLinkListNode(data);
//第一步先对新节点指针域赋值---赋当前tempP中指针域中的数值
newPtr->nextNodeAddress = tempP->nextNodeAddress;
//第二步对于位置前一个节点指针域进行赋值
tempP->nextNodeAddress = newPtr;
return true;
}
tempP= tempP->nextNodeAddress;
}
return false; //插入失败
}
/***********************************************************
函数功能:根据ID(学号)选择位置添加节点
函数名称:bool insertSpecifyIDLinkListNode(LINKLIST *headPtr,Data data)
函数参数:IDNum数据 data数据
返 回 值:成功true 不成功false
该函数能够完成排序动作
************************************************************/
bool insertSpecifyIDLinkListNode(LINKLIST *headPtr,Data data)
{
//辅助指针 如果对指针进行偏移时需要使用辅助指针
LINKLIST *tempP = headPtr->nextNodeAddress;
//修补漏洞 如果只有表头 需要单独做
if(tempP ==NULL)
return appendLinkListNode(headPtr,data);
while(tempP!= NULL)
{
LINKLIST * cmpNextP = tempP->nextNodeAddress;
//学号11号 要插到10与12号之间 但是12号没有10就是终止
if((cmpNextP == NULL) && (tempP->data.id <= data.id) )
{
//创建新节点
LINKLIST *newPtr = createNewLinkListNode(data);
tempP->nextNodeAddress = newPtr;
return true;
}
//学号11 要插到10号与12号之间 10号与12都必须存在
else if((tempP->data.id <= data.id) && (data.id <= cmpNextP->data.id))
{
//创建新节点
LINKLIST *newPtr = createNewLinkListNode(data);
newPtr->nextNodeAddress = tempP->nextNodeAddress;
tempP->nextNodeAddress = newPtr;
return true;
}
tempP = tempP->nextNodeAddress;
}
return false;
}
/***********************************************************
函数功能:查看所有节点的数据
函数名称:void lookAllLinkListNode(LINKLIST *headPtr)
函数参数:头结点地址
返 回 值:无
************************************************************/
void lookAllLinkListNode(LINKLIST *headPtr)
{
//辅助指针 如果对指针进行偏移时需要使用辅助指针
LINKLIST *tempP = headPtr->nextNodeAddress;
while(tempP!= NULL)
{
printf("学号:%d %s %d %d %f\n",tempP->data.id,tempP->data.name,tempP->data.age,tempP->data.sex,tempP->data.chengji);
tempP = tempP->nextNodeAddress;
}
}
/***********************************************************
函数功能:指定ID对链表查询
函数名称:Data specifyIdFromLinkList(LINKLIST *headPtr,int id)
函数参数:头结点地址 id学号
返 回 值:成功true 失败false
************************************************************/
Data specifyIdFromLinkList(LINKLIST *headPtr,int id)
{
Data NULLDATA ={0};
//辅助指针
LINKLIST * tempP = headPtr->nextNodeAddress;
while(tempP != NULL)
{
if(id == tempP->data.id)
{
return tempP->data;
}
tempP = tempP->nextNodeAddress;//遍历下一个节点
}
return NULLDATA;
}
/***********************************************************
函数功能:指定ID对节点进行修改内部数据
函数名称:bool updateSpecifyIdFromLinkListNode(LINKLIST *headPtr,int id)
函数参数:头结点地址 data新的数据
返 回 值:成功true 失败false
************************************************************/
bool updateSpecifyIdFromLinkListNode(LINKLIST *headPtr,Data data)
{
LINKLIST * tempP= headPtr->nextNodeAddress;
while(tempP != NULL)
{
if(data.id == tempP->data.id)
{
tempP->data = data; //数据更新
return true;
}
tempP = tempP->nextNodeAddress;
}
return false;
}
/***********************************************************
函数功能:删除指定ID节点
函数名称:bool deleteSpecifyIdFromLinkListNode(LINKLIST *headPtr,int id)
函数参数:头结点地址 id新的数据
返 回 值:成功true 失败false
************************************************************/
bool deleteSpecifyIdFromLinkListNode(LINKLIST *headPtr,int id)
{
LINKLIST * tempP= headPtr; //应该指向表头
while(tempP != NULL)
{
//站在node2中看node3中内容 需要将node3进行删除
LINKLIST * delNodeAddress = tempP->nextNodeAddress;
if((delNodeAddress != NULL)&&(delNodeAddress->data.id == id))
{
//将3号指针域数据赋值给2号指针域 2号与4号即可联系 3号抛在一遍
tempP->nextNodeAddress = delNodeAddress->nextNodeAddress;
//将3号节点指针域置为NULL 将数据域进行清空操作
delNodeAddress->nextNodeAddress = NULL;
memset(&delNodeAddress->data,0,sizeof(Data));//清空
return true;
}
tempP = tempP->nextNodeAddress;
}
return false;
}
/************************************学生管理系统相关功能函数************************************/
/***********************************************************
函数功能:选项打印函数信息
函数名称:void printStudentSystemMenu(void)
函数参数:stu为学生信息
返 回 值:成功true 失败false
************************************************************/
void printStudentSystemMenu(void)
{
printf("********欢迎使用信盈达学生管理系统*********\n");
printf("*************1.添加学生信息****************\n");
printf("*************2.修改学生信息****************\n");
printf("*************3.删除学生信息****************\n");
printf("*************4.查询学生信息****************\n");
printf("***********5.退出学生管理系统**************\n");
printf("*******************************************\n");
printf("请输入1-5的数字:\n");
}
/***********************************************************
函数功能:添加学生信息
函数名称:bool addStudentInfo(void)
函数参数:无
返 回 值:成功true 失败false
************************************************************/
bool addStudentInfo(void)
{
Data stu;
printf("请输入学号信息:\n");
scanf("%d",&stu.id);
getchar();
printf("请输入姓名:\n");
gets(stu.name);
printf("请输入性别:\n");
scanf("%d",&stu.sex);
printf("请输入年龄:\n");
scanf("%d",&stu.age);
printf("请输入成绩:\n");
scanf("%f",&stu.chengji);
//进行存储至数组/链表---------------------分层结构 输入 处理 输出
int val = insertSpecifyIDLinkListNode(headNodePtr,stu);
val > 0?printf("添加成功\n"):printf("添加失败\n");
return val;
}
/***********************************************************
函数功能:修改学生信息
函数名称:bool updateStudentInfo(void)
函数参数:无
返 回 值:成功true 失败false
************************************************************/
bool updateStudentInfo(void)
{
Data stu;
printf("请输入学号信息:\n");
scanf("%d",&stu.id);
getchar();
printf("请输入姓名:\n");
gets(stu.name);
printf("请输入性别:\n");
scanf("%d",&stu.sex);
printf("请输入年龄:\n");
scanf("%d",&stu.age);
printf("请输入成绩:\n");
scanf("%f",&stu.chengji);
//进行存储至数组/链表 修改
int val = updateSpecifyIdFromLinkListNode(headNodePtr,stu);
val > 0?printf("修改成功\n"):printf("修改失败\n");
return val;
}
/***********************************************************
函数功能:删除学生信息
函数名称:bool deleteStudentInfo(void)
函数参数:无
返 回 值:成功true 失败false
************************************************************/
bool deleteStudentInfo(void)
{
Data stu;
printf("请输入学号信息:\n");
scanf("%d",&stu.id);
getchar();
//进行存储至数组/链表 删除
int val = deleteSpecifyIdFromLinkListNode(headNodePtr,stu.id);
val > 0?printf("删除成功\n"):printf("删除失败\n");
return val;
}
/***********************************************************
函数功能:查询所有学生信息
函数名称:bool lookAllStudentInfo(void)
函数参数:无
返 回 值:成功true 失败false
************************************************************/
bool lookAllStudentInfo(void)
{
lookAllLinkListNode(headNodePtr);
return true;
}
/***********************************************************
函数功能:查询指定学生信息
函数名称:bool lookStudentInfo(void)
函数参数:无
返 回 值:成功true 失败false
************************************************************/
bool specifyIdStudentInfo(void)
{
Data stu;
printf("请输入学号信息:\n");
scanf("%d",&stu.id);
getchar();
stu = specifyIdFromLinkList(headNodePtr,stu.id);
if(stu.id != 0)
{
printf("学号:%d %s %d %d %f\n",stu.id,stu.name,stu.age,stu.sex,stu.chengji);
return true;
}
else
{
return false;
}
}
//枚举操作
enum Option{ADDSTUDENT=1,UPDATESTUDENT,DELETESTUDENT,SELECTSTUDENT,EXITSTUDENT};
int main()
{
Data input;
headNodePtr=createNewLinkListNode(input);//创建头指针或者头节点
while(1)
{
int inputOption=0;
printStudentSystemMenu();
scanf("%d",&inputOption);
switch(inputOption)
{
case ADDSTUDENT:addStudentInfo();break;
case UPDATESTUDENT:updateStudentInfo();break;
case DELETESTUDENT:deleteStudentInfo();break;
case SELECTSTUDENT:specifyIdStudentInfo();break;
case EXITSTUDENT:return 1;
}
}
return 0;
}

