C语言学习笔记

前言

适应期最后一周马上就要结束了,回顾这一周的学习生活,只能用痛并快乐来形容。刚开始上手学习C语言感觉还是比较简单易懂的前面的声明 习惯 数据类型都非常好理解 学习进度一日千里,当时我对自己充满了自信。然后三天后 第一周作业出现了,看到作业的时候我是拒绝的,因为上面写的很多东西我是想不到该去怎么实现的。 三天的学习 ,很多知识在脑海里只是走马观花的过了一遍, 没有留下什么深刻的记忆。 但是摆烂是没有用的 ,只能硬着头皮做了。于是第一周的痛苦生活就开始了,反复翻阅慕课网 去思考怎么去实现每一个需求,晚上也是辗转反侧去思考BUG是怎么出来的。也是功夫不负有心人,我也顺利的完成了大多数要求,还受到了学姐的表扬(划重点)。

这篇笔记是我自己整理而来 主题部分是慕课上的教学 ,和其他网站上的内容 再加上我自己所理解的一些心得,才疏学浅 ,如有错误请纠正指出。

习惯

写代码应该有一个良好的习惯 使你的代码具有良好的可读性 我总结有以下几点

1.一个说明或一个语句占一行。

2.函数体内的语句要有明显缩进通常以按一下Tab键为一个缩进

3.括号要成对写,如果需要删除的话也要成对删除

4.多写注释,一方面可以让别人知道程序怎样实现的功能,另一方面也让你以后能了解当时自己是怎么想的(不写注释的话 这代码恐怕只有上帝能看懂)

5.要有多使用函数、养成模块化编程的习惯,用函数来封装一个个代码块 使得功能模块能够复用 可以有效缩小代码体积 移植性强 main函数上的代码更加直观。

6.命名方式很重要,要让人看到这个变量就能直观的明白要表达什么意思 尽量使用有具体意义的名词去命名。

C语言的特点

C 语言是一种通用的高级语言

特点:

  • 易于学习。
  • 面向过程编程
  • 产生高效率的程序。
  • 可以处理底层的活动。
1
2
3
4
5
6
#include<stdio.h> 
int main()
{
printf("哦天哪 这不是Hello world么");
return 0;
}

C程序结构

一个C程序就是由若干头文件和函数组成。

1
2
3
4
5
6
7
#include<stdio.h> //头文件

int main()//主函数
{
printf("哦天哪 这不是Hello world么");//格式化输出语句
return 0;
}

#include <stdio.h>是一条预处理命令,它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作。

函数是实现代码逻辑的一个小的单元。

C程序就是执行主函数里的代码,也可以说这个主函数就是C语言中的唯一入口。并且C程序一定是从主函数开始执行的。

标识符

编程时给变量或者函数起的名字就是标识符

C 语言规定,标识符可以是**字母(A~Z,a~z)数字(0~9)下划线_**组成的字符串,并且**第一个字符必须是字母或下划线**。在使用标识符时还有注意以下几点:

(1)标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效,当两个标识符前8位相同时,则被认为是同一个标识符。

(2)标识符是严格区分大小写的。例如Imooc和imooc 是两个不同的标识符。

(3)标识符最好选择有意义的英文单词组成做到”见名知意”,不要使用中文。

(4)标识符不能是C语言的关键字

关键字

下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。

关键字 说明
auto 声明自动变量
break 跳出当前循环
case 开关语句分支
char 声明字符型变量或函数返回值类型
const 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
continue 结束当前循环,开始下一轮循环
default 开关语句中的”其它”分支
do 循环语句的循环体
double 声明双精度浮点型变量或函数返回值类型
else 条件语句否定分支(与 if 连用)
enum 声明枚举类型
extern 声明变量或函数是在其它文件或本文件的其他位置定义
float 声明浮点型变量或函数返回值类型
for 一种循环语句
goto 无条件跳转语句
if 条件语句
int 声明整型变量或函数
long 声明长整型变量或函数返回值类型
register 声明寄存器变量
return 子程序返回语句(可以带参数,也可不带参数)
short 声明短整型变量或函数
signed 声明有符号类型变量或函数
sizeof 计算数据类型或变量长度(即所占字节数)
static 声明静态变量
struct 声明结构体类型
switch 用于开关语句
typedef 用以给数据类型取别名
unsigned 声明无符号类型变量或函数
union 声明共用体类型
void 声明函数无返回值或无参数,声明无类型指针
volatile 说明变量在程序执行中可被隐含地改变
while 循环语句的循环条件

分类如下:

数据类型关键字(12个):

char、short、int、long、signed、unsigned、float、double、struct、union、enum、void

控制语句关键字(12个):

1、循环控制(5个)

for、do、while、break、continue

2、条件语句(3个)

if、else、goto

3、开关语句(3个)

switch、case、default

4、返回语句(1个)

return

存储类型关键字(5个)

auto、extern、register、static、typedef

其他关键字(3个)

const、sizeof、volatil

运算符

  1. 算术运算符:加+、减-、乘*、除/、取余%、自增++、自减–;

  2. 赋值运算符:

    ①简单赋值运算符:=,右值赋给左变量

    ②符合赋值:+=、-=、*=、/=、%=

    注意:连续复合赋值运算:自右向左运算

  3. 关系运算符:>、>=、<、<=、==、!=(不等于)

  4. 逻辑运算符:&&与 ||或 非!

  5. 三目运算符(表达式1?表达式2:表达式3)

    表达式1为真执行表达式2,为假执行表达式3

运算符优先级

第一优先级: [ ] ( ) . -> //“.“为成员选择(对象),”->”为成员选择(指针);

第二优先级: 负号运算符- ++ – * & ! 强制类型转换(类型) sizeof

第三优先级:/ * %

第四优先级: + -

第五优先级:左移<< 右移>>

第六优先级:> >= < <=

第七优先级:== !=

第八:按位与& 第九:按位异或^ 第十:按位或| 第十一:&& 第十二:|| 第十三:三目运算符( ? : )

第十四:赋值运算符

第十五:逗号运算符(逗号也是运算符他的优先级最低)

数据类型

一、整型(int、short、long、long long)

1、有符号整型

有符号整型的数据类型通常包括 int、short、long、long long 四种,因为是有符号类型,所以前面要加上 signed ,但是通常省略,也就是说在代码中直接打出 int 类型就代表是有符号类型的。

(1)int类型
数据类型大小是 4 字节,能表示的数值范围是
-2^(32-1) – 2^(32-1)-1 (即 -2147483648 ~ 2147483647)
打印类型是 %d ,使用格式为 int 名 = 值;

(2)short类型
数据类型大小是 2 字节,能表示的数值范围是
-2^(16-1) – 2(16-1) -1 (即 -32768 ~ 32767)
打印类型是 %hd ,使用格式为 short 名 = 值;

(3)long类型
数据类型大小是 4 字节,能表示的数值范围是
-2^(32-1) – 2^(32-1)-1 (即 -2147483648 ~ 2147483647)
打印类型是 %ld ,使用格式为 int 名 = 值;

(4)long long类型
数据类型大小是 8 字节,能表示的数值范围是
-2^(63) ~ 2^(63)-1 (这个数足够大了)
打印类型是 %lld ,使用格式为 long long 名 = 值;

2、无符号整型

无符号数用 unsigned 表示 ,只表示数据量,而没有方向(没有正负,且无符号数最高位不是符号位,而就是数的一部分,无符号数不可能是负数。

(1)unsigned int 类型
数据类型大小是 4 字节,能表示的数值范围是
0 – 2^(32)-1 (即 0~4294967295)
打印类型是 %u ,使用格式为 unsigned int 名 = 值;

(2)unsigned short 类型
数据类型大小是 2 字节,能表示的数值范围是
0 ~ 2^8 -1 (即 0~65535)
打印类型是 %hu ,使用格式为 unsigned short 名 = 值;

(3)unsigned long 类型
数据类型大小是 4 字节,能表示的数值范围是
0 – 2^(32)-1 (即 0~4294967295)
打印类型是 %lu ,使用格式为 unsigned long 名 = 值;

(4)unsigned long long 类型
数据类型大小是 8 字节,能表示的数值范围是
0~2^63-1
打印类型是 %llu ,使用格式为 unsigned long long 名 = 值;

二、 字符型(char)

字符型变量用于存储一个单一字符,在 C 语言中用 char 表示,其中每个字符变量都会占用 1 个字节。在给字符型变量赋值时,需要用一对英文半角格式的单引号(’ ‘)把字符括起来。字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的 ASCII 编码放到变量的存储单元中。char的本质就是一个1字节大小的整型。

char 的格式匹配符(打印格式) 为:%c

数值表示范围是:
有符号: -2^(8-1) – 2(8-1) -1 (即 -128 ~ 127)
无符号: 0 ~ 2^8 -1 (即 0~255)

常用的ASCLL码为:
‘A’:65

‘a’:97(大小写相差 32 )

‘0’:48

‘\n’:10

‘\0’: 0

三、浮点型(float、double)

1、单精度浮点型(float)

单精度浮点型的大小是 4 字节
float v1 = 4.345;
unsigned float v1 = 4.345; 无符号的 float 数据
格式匹配符是:%f , 默认保留 6 位小数。

2、双精度浮点型(double)

双精度浮点型的大小为 8 字节
double v2 = 5.678;
unsigned double v2 = 5.678; 无符号的 double 数据

printf(“n = %08.3f\n”, n);
输出的含义为:显示8位数(包含小数点), 不足8位用0填充。并且保留3位小数。对第4位做四舍五入。

四、void型

void 类型指定没有可用的值。它通常用于以下三种情况下:

序号 类型与描述
1 函数返回为空 C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status);
2 函数参数为空 C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);
3 指针指向 void 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。

变量与常量

变量

顾名思义,就是可以发生改变的量就叫做变量

变量有一个概念就是先定义,再使用

常量

不会改变的量就叫做常量

字面常量,可分为整型常量,实型常量,字符常量和字符串常量。举例:如10,20和30都是整型常量;如 2.3,4.5和7.7都是实型常量;如 ‘a’,‘b’ 和 ‘c’都是字符常量;如 “abc”是字符串常量。

在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。符号常量在使用之前必须先定义,其一般形式为:

1
2
3
4
5
6
7
8
9
10
11
12
#define 标识符 常量值 


#include <stdio.h>
#define POCKETMONEY 10 //定义常量及常量值
int main()
{
// POCKETMONEY = 12; //小明私自增加零花钱对吗?
printf("小明今天又得到%d元零花钱
", POCKETMONEY);
return 0;
}

输入与输出

输入

1.使用scanf()函数进行输入

scanf()函数的原型是

1
int scanf(const char * restrict format,...);

scanf()函数位于C标准库头文件<stdio.h>中
format 是 类似于 %d %s %e %f %lf %lld之类的格式串
格式后面按格式串里面定义的先后顺序 依次书写变量的地址 如&a和&b 但是字符数组的变量名就代表了该字符数组的首地址了,不用加上&取地址运算符

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
//声明了两个整形变量a和b
int a=0,b=0;
//输入两个整数并赋值给整形变量a和b
scanf("%d %d",&a,&b);
//计算a+b的值并输出
printf("a+b=%d\n", a+b);
return 0;
}

2.scanf() 常见的格式控制符汇总

格式控制符 含义
%d %ld %lld 输入一个int型整数 输入一个long型整数 输入一个long long型整数
%c 输入一个字符
%s 输入一个字符串(遇到空格截断)
%f %lf 输入一个float单精度浮点数 输入一个double双精度浮点数
%o %x %u 输入一个八进制整数到int型变量 输入一个十六进制整数到int型变量 输入一个无符号整数到int型整数
%e %le 输入一个指数形式的小数到float单精度浮点数 输入一个指数形式的小数到double单精度浮点数

3.fscanf()是文件输入的函数

4.getchar()是从输入流中读取一个字符的函数

1
2
char ch;
getchar(ch);

getchar()函数位于C标准库头文件<stdio.h>中
后续的getchar()调用是不会等待用户按键,而直接读取缓冲区中的字符

5.getch()是从控制台中读取一个字符的函数

1
2
char ch;
getch(ch);

getch()函数位于C标准库头文件<conio.h>中
getch()调用会等待用户按键,从控制台读入字符 并不会回显到控制台

6.getche()是从控制台中读取一个字符的函数

1
2
char ch;
getche(ch);

getche()函数位于C标准库头文件<conio.h>中
getche()调用会等待用户按键,从控制台读入字符 并立刻回显到控制台

7.gets()是从输入缓冲区读入一个字符串赋值到指定的字符数组中

1
2
char str[2048];
gets(str);

上述代码基本等价于

1
scanf("%s", str);

但是使用

1
scanf("%s", str);

遇到空格会截断字符串

使用

1
gets(str);

遇到空格不会截断字符串
gets()函数位于C标准库头文件<stdio.h>中
gets() 函数的功能是从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间。
gets() 可以直接用于输入带空格的字符串,遇到换行符才结束输入

输出

1.使用printf()函数进行输出

printf()函数的原型为

1
int printf(const char *format, ...);

printf()函数位于C标准库头文件<stdio.h>中
printf的控制格式和scanf是差不多的 printf还可以输出相关的转义字符

1
2
3
4
5
6
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}

printf中想要输出’‘必须要使用转义字符’’才能输出成功

1
printf("Hello World!\\");

就能输出Hello World!\

2.printf的输出格式

输出格式 含义
%d %ld %lld 十进制有符号整数 对应int型变量/long型变量/long long型变量
%u 十进制无符号整数int型变量
%f %lf 单精度浮点数 双精度浮点数
%s 字符数组(串)
%c 一个字符
%p 指针指向的内存地址
%e 指数形式的单精度浮点数

3.printf的输出的格式控制

1、%d的格式控制格式:
%对齐方式+最长输出位数d
对齐方式:-表示向左对齐 默认向右对齐
如%Nd: N是指定输出的宽度。默认是向右对齐,整形位数小于N位时,,空位补上空格;大于N位时,按实际位数输出。想输出01,可以使用%01d控制输出
2、%f的格式控制格式:
%对齐方式+最长位数.小数点后位数f
对齐方式:-表示向左对齐 默认向右对齐
如%k.nf:默认是向右对齐,最大长度为k位,小数位为n位,整数为则为k-n-1位。 %4.2f 输出类似12.13这样的单精度浮点数 也可以让i留白 %.2f 输出xxxxx. 02这样的单精度浮点数 此时总位数是不定的值

4.fprintf()是输出到文件的函数

分支结构

1、简单if语句

C语言中的分支结构语句中的if条件语句。

1
2
3
4
if()
{
执行代码块;
}

如果表达式的值为真,则执行其后的语句,否则不执行该语句。

注意:if()后面没有分号,直接写{}

2、简单if-else语句

1
2
3
4
5
6
7
8
if(表达式)
{
执行代码块1;
}
else
{
执行代码块2;
}

如果表达式的值为真,则执行代码块1,否则执行代码块2。

注意:
if()后面没有分号,直接写{},else后面也没有分号,直接写{}

3、多重if-else语句

依次判断表达式的值,当出现某个值为真时,则执行对应代码块,否则执行代码块n。

1
2
3
4
5
6
7
8
9
10
11
12
if()
{
xxx;
}
if else()
{
xxx;
}
else
{
xxx;
}

4、嵌套if-else语句

C语言中嵌套if-else语句。嵌套if-else语句的意思,就是在if-else语句中,再写if-else语句。

5、switch语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch(表达式)
{
case 常量表达式1:
执行代码块;
break;

......
......

case 常量代码块n:
执行代码块;
break;

default:
执行代码块n+1;
}

注意:

在case后的各常量表达式的值不能相同,否则会出现错误。
在case子句后如果没有break;会一直往后执行**一直到遇到break;才会跳出switch语句。
switch后面的表达式语句只能是整型或者字符类型。
在case后,允许有多个语句,
可以不用{}**括起来。
各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
default子句可以省略不用。

6、goto语句

C语言中也有这样的语句,就是goto语句,goto语句是一种无条件分支语句。

臭名远扬。

循环结构

1、while循环

反复不停的执行某个动作就是循环

1
2
3
4
while(表达式)
{
执行代码块;
}

其中表达式表示循环条件,执行代码块为循环体。

计算表达式的值,当值为真(非0)时, 执行循环体代码块。

while语句中的表达式一般是关系表达或逻辑表达式,当表达式的值为假时不执行循环体,反之则循环体一直执行。

一定要记着在循环体中改变循环变量的值,否则会出现死循环(无休止的执行)。

循环体如果包括有一个以上的语句,则必须用{}括起来,组成复合语句。

2、do-while循环

C语言中的do-while循环

1
2
3
4
do
{
执行代码块;
}while(表达式);

它先执行循环中的执行代码块,然后再判断while中表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。

注意:使用do-while结构语句时,while括号后必须有分号。

3、for循环

c语言中for循环

1
2
3
4
for(执行表达式1;判断表达式2;执行表达式3)
{
执行代码块;
}

执行表达式1,对循环变量做初始化;

判断表达式2,若其值为真(非0),则执行for循环体中执行代码块,然后向下执行;若其值为假(0),则结束循环;

执行表达式3,(i++)等对于循环变量进行操作的语句;

执行for循环中执行代码块后执行第二步;第一步初始化只会执行一次。
循环结束,程序继续向下执行。

注意:for循环中的两个分号一定要写

在for循环中:

表达式1是一个或多个赋值语句,它用来控制变量的初始值;

表达式2是一个关系表达式,它决定什么时候退出循环;

表达式3是循环变量的步进值,定义控制循环变量每循环一次后按什么方式变化。

这三部分之间用分号 ; 分开。

for循环中的“表达式1、2、3”均可不写为空,但两个分号(;;)不能缺省。

省略“表达式1(循环变量赋初值)”,表示不对循环变量赋初始值。

省略“表达式2(循环条件)”,不做其它处理,循环一直执行(死循环)。

省略“表达式3(循环变量增减量)”,不做其他处理,循环一直执行(死循环)。

表达式1可以是设置循环变量的初值的赋值表达式,也可以是其他表达式。

表达式1和表达式3可以是一个简单表达式也可以是多个表达式以逗号分割。

表达式2一般是关系表达式或逻辑表达式,但也可是数值表达式或字符表达式,只要其值非零,就执行循环体。

各表达式中的变量一定要在for循环之前定义。

4、三种循环比较

while, do-while和for三种循环在具体的使用场合上是有区别的,如下:

在知道循环次数的情况下更适合使用for循环;

在不知道循环次数的情况下适合使用while或者do-while循环:

如果有可能一次都不循环应考虑使用while循环

如果至少循环一次应考虑使用do-while循环。

但是从本质上讲,while,do-while和for循环之间是可以相互转换的。

5、循环控制语句

循环控制语句改变你代码的执行顺序。通过它你可以实现代码的跳转。

控制语句 描述
break 语句 终止循环switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。
continue 语句 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
goto 语句 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

函数

(1)函数调用

在C语言中,函数调用的一般形式为:

函数名([参数]);

(2)有参函数和无参函数

在函数中不需要函数参数的称之为无参函数,在函数中需要函数参数的称之为有参函数

形参与实参

函数的参数分为形参实参两种,形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数

实参是在调用时传递该函数的参数

在参数传递时,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配”的错误

(3)递归函数

即在一个函数中调用这个函数,举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h> 
int factorial(int n)
{
int result;
if (n<0)
{
printf("输入错误!\n");
return 0;
}
else if (n==0 || n==1)
{
result = 1;
}
else
{
result = factorial(n-1) * n;
}
return result;
}

int main(){
int n = 5;
printf("%d的阶乘=%d",n,factorial(n));
return 0;
}

递归函数必须有结束条件

数组

简而言之,就是把一堆数据类型相同的数据放在一起

形式为

**数据类型 数组名称[长度]; **

初始化数组

数据类型 数组名称[长度n] = {元素1,元素2…元素n};

获取数组元素时: 数组名称[元素所对应下标];

如:初始化一个数组 int arr[3] = {1,2,3}; 那么arr[0]就是元素1。

元素引用
1
2
int arr[3]={1,2,3};
t=arr[2];//t=3
数组的遍历

二话不说,直接上例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int main() {
int i;
int arr[10];
printf("请输入十个数字,我会把他从大到小排序:\n");
for (i = 0; i < 10; i++) {
scanf("%d", &arr[i]);
}
{
int i, j, temp;
for (i = 0; i < 10; i++) {
for (j = 0; j < 10 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
for (i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
二维数组

二维数组就像一个表格,它的性质和一维数组是一样的

比如我存入十六个数字{1,2,6,5,9,8,7,4,5,6,5,5,6,6,6,5}

int arr[4] [4]={1,2,6,5,9,8,7,4,5,6,5,5,6,6,6,5}

那么这个二维数组有四行四列,即

列数/行数 第一列 第二列 第三列 第四列
第一行 1 2 6 5
第二行 9 8 7 4
第三行 5 6 5 5
第四行 6 6 6 5

注:行数可以不写,但是列数必须写

注2:例如用数组储存单词,一个字母就对应一个元素

指针

1.基本概念

1.1地址的理解

整型占四个字节,图中四个方格可以看作存放整型a的区域,则第一个方格的位置100就是a的地址

1.2为什么使用指针

在你想要调用某个数时,可以把那个数据当作你的另一半。你可以在茫茫人海中大范围寻找,可想而知很难。但是有一个角色叫做媒婆对吧?媒婆可以作为媒介将你和你的另一半介绍到一起,我们就不需要大范围找啦,只需要根据媒婆的指引直接就可以找到另一半。媒婆的作用其实就是指针的作用。同样,在我们传入形参时,若传入的数据过于庞大,我们只需要将其指针传入,在运行时系统就可以根据指针直接到对应的位置找到需要的数据,就方便多了。

2.指针的定义

int *p=&a;

在声明指针的形式中,这里的星是起到了说明的作用,说明声明的变量p是一个指针。不要将其和p联系在一 起,觉得*p是一个数,并不是一个指针。可以这样理解:

int *(p=&a)

“*”是说明的作用,括号中&取地址将a的地址取出来赋予p,声明的是一个指针p。

注意:

1、避免野指针:

1.在声明指针变量时就赋予其地址

2.在声明指针变量时先赋予NULL

3.避免指针的访问越界4.避免返回局部变量的地址

2、避免访问空指针

3.指针的运算

p++

代表的是地址往后移动一个单位,指向下一个数据,运算之后得到的还是一个指针

(*p)++

代表的是地址后移之后再取出该地址之中存的数据,其结果是一个数据。

*(p++)

代表将原地址中存储的数据取出来再加一。

4.指针和数组

4.1返回值是指针的函数

指针函数**:
函数的返回值的类型既可以是整型(若没有设定,则默认为整型),实型,字符型,也可以是指针型。
返回值为指针类型的函数又称为指针类型的函数,建成指针函数。

声明一个指针函数:

int a(int x)首先由单目运算符,a与(int x)结合,表示这是一个函数,int x 表示是一个参数传入,前面的表示这是一个指针函数,返回值是指针。

4.2指向函数的指针变量

定义形式:
类型说明符 (*变量名) (传参列表)

指向函数的指针变量在声明的时候,并不指向某个具体的函数,而是一个空指针。

关于c语言的结构体:

为什么要用到结构体,我们都已经学了很多int char …等类型还学到了同类型元素构成的数组,以及取上述类型的指针,在一些小应用可以灵活使用,然而,在我们实际应用中,每一种变量进行一次声明,再结合起来显然是不太实际的,类如一位学生的信息管理,他可能有,姓名(char),学号(int)成绩(float)等多种数据。如果把这些数据分别单独定义,就会特别松散、复杂,难以规划,因此我们需要把一些相关的变量组合起来,以一个整体形式对对象进行描述,这就是结构体的好处。

关于结构体的小知识

2.1只有结构体变量才分配地址,而结构体的定义是不分配空间的。
2.2结构体中各成员的定义和之前的变量定义一样,但在定义时也不分配空间。
2.3结构体变量的声明需要在主函数之上或者主函数中声明,如果在主函数之下则会报错
2.4c语言中的结构体不能直接进行强制转换,只有结构体指针才能进行强制转换
2.5相同类型的成员是可以定义在同一类型下的
列如

1
2
3
4
5
6
struct Student
{
int number,age;//int型学号和年龄
char name[20],sex;//char类型姓名和性别
float score;
};

3关于结构体变量的定义和引用

在编译时,结构体的定义并不分配存储空间,对结构体变量才按其数据结构分配相应的存储空间

1
2
3
4
5
6
7
8
9
10
11
`` struct Book`
`{`
`char title[20];//一个字符串表`

`示的titile 题目`
`char author[20];//一个字符串表示的author作者`
`float value;//价格表示`
`};//这里只是声明 结构体的定义`
struct Book book1,book2;//结构体变量的定义 分配空间

book1.value;//引用结构体变量`

定义结构体变量以后,系统就会为其分配内存单元,比如book1和book2在内存中占44个字节(20+20+4)具体的长度可以在你的编译器中使用sizeof关键字分别求出来。

要注意一点:用sizeof关键字求结构体长度时,返回的最大基本类型所占字节的整数倍 比方说我们上面求得的为44 为 float(4个字节)的整数倍,
但是我们把title修改为title[22]; 这时正常长度为46 ,但是你会发现实际求得的为48,(4的整数倍)

这就涉及到结构体的存储:

1结构体整体空间是占用空间最大的成员(的类型)所占字节数的整数倍。

2.结构体的每个成员相对结构体首地址的偏移量(offset)都是最大基本类型成员字节大小的整数倍,如果不是编译器会自动补齐,

1.偏移量—-偏移量指的是结构体变量中成员的地址和结构体变量首地址的差。即偏移字节数,结构体大小等于最后一个成员的偏移量加上他的大小,第一个成员的偏移量为0,

1
2
3
4
5
6
7
8
9
struct S1
{
char a;

int b;

double c;

};

这里char a 偏移量为1 之后为int b 因为偏移量1不为int(4)的整数倍,所以会自动补齐,而在 double c 时,偏移量为8 是double(8)的整数倍,所以不用自动补齐 最后求得结构体得大小为 16

具体看下图:这里char a 偏移量为1 之后为int b 因为偏移量1不为int(4)的整数倍,所以会自动补齐,而在 double c 时,偏移量为8 是double(8)的整数倍,所以不用自动补齐 最后求得结构体得大小为 16

具体看下图:
在这里插入图片描述

4结构体变量的初始化

结构体的初始化有很多需要注意的地方,这里我们说明下
首先是几种初始化的方法
ps:在对结构体变量初始化时,要对结构体成员一一赋值,不能跳过前面成员变量,而直接给后面成员赋初值,但是可以只赋值前面几个,对与后面未赋值的变量,如果是数值型,则会自动赋值为0,对于字符型,会自动赋初值为NULL,即‘\0’

4.1定义时直接赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Student`
`{`
`char name[20];`
`char sex;`
`int number;`
`}stu1={"zhaozixuan",'M',12345};`
`//或者`
`struct Student`
`{`
`char name[20];`
`char sex;`
`int number;`
`};`
`struct Student stu1={"zhaozixuan",'M',12345};`

**`注意字符为‘ ’ 字符串为""**

4.2定义结构体之后逐个赋值

1
2
3
4
5
`stu1.name="王伟";`
`stu1.sex='M';`
`stu1.number=12305;`
`//也可用strcpy函数进行赋值`
`strcpy(stu1.name,"王伟");`

4.3定义之后任意赋值

1
2
3
4
5
``struct Student stu1={`
`.name="Wang",`
`.number=12345,`
`.sex='W',`
};//可以对任意变量赋值`

结构体变量的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Student
{ char name[20];
char sex;
int number;
struct Date
{
int year;
int month;
int day;
}birthday;

}stu1;
scanf("%d",&stu1.birthday.month);

结构体数组与结构体变量区别只是将结构体变量替换为数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Student
{
char name[20];
char sex;
int number;
}stu1[5]={
{"zhaozixuan",'M',12345},
{"houxiaohong",'M',12306},
{"qxiaoxin",'W',12546},
{"wangwei",'M',14679},
{"yulongjiao",'W',17857}
};
stu1[3].name[3]

访问结构成员

为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。可以使用 struct 关键字来定义结构类型的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */

/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);

/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
5
6
7
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

结构作为函数参数
可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。可以使用上面实例中的方式来访问结构变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

/* 函数声明 */
void printBook( struct Books book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */

/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/* 输出 Book1 信息 */
printBook( Book1 );

/* 输出 Book2 信息 */
printBook( Book2 );

return 0;
}
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
5
6
7
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700

指向结构的指针
可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似

struct Books *struct_pointer;
可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

/* 函数声明 */
void printBook( struct Books *book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */

/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/* 通过传 Book1 的地址来输出 Book1 信息 */
printBook( &Book1 );

/* 通过传 Book2 的地址来输出 Book2 信息 */
printBook( &Book2 );

return 0;
}
void printBook( struct Books *book )
{
printf( "Book title : %s\n", book->title);
printf( "Book author : %s\n", book->author);
printf( "Book subject : %s\n", book->subject);
printf( "Book book_id : %d\n", book->book_id);
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
5
6
7
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700

简单文件操作

文件的打开

基本格式:

1
2
3
FILE *fp;
fp = fopen(filename,mode); //filename 输入文件的地址 mode 输入操作模式
fclose(fp); //用了fopen一定要使用fclose进行关闭(代码规范)

mode的操作模式

mode字符串对应的操作模式:

读写方式 文件类型 含义 读写方式 文件类型 含义
r 文本文件 打开文本,只读 rb+ 二进制文件 打开二进制文件,读、覆盖写
w 文本文件 建立文本文件,只写 wb+ 二进制文件 打开二进制文件,先写后读
a 文本文件 打开文本文件,追加 ab+ 二进制文件 打开二进制文件,读,追加
rb 二进制文件 打开二进制文件,只读 rt 文本文件 打开文本文件,只读
wb 二进制文件 建立二进制文件,只写 wt 文本文件 建立文本文件,只写
ab 二进制文件 打开二进制文件,读、追加 at 文本文件 打开文本文件,追加
r+ 文本文件 打开文本文件,读、覆盖写 rt+ 文本文件 打开文本文件,读、覆盖写
w+ 文本文件 打开文本文件,先写后读 wt+ 文本文件 打开文本文件,先写后读
a+ 文本文件 打开文本文件,读、追加 at+ 文本文件 打开文本文件,读、追加

三个基本模式:
“r”(read)模式总是打开一个已经存在的文件,如果文件不存在则出错。
“w”(write)模式建立一个新文件,如果文件已经存在,那么先删除存在的文件,再建立新文件
“a”(append)打开一个存在的文件,在文件的尾部追加数据。

三个追加符:
“b”(binary)表示二进制文件。
“t”(或默认)表示文本文件。
“+”表示将模式扩展为可读、可写方式。

注意:r+ 和 w+ 的区别是 找不到文件时 r+ 会返回NULL 无法进行读写 ;w+ 会添加一个文件并进行写读

文件的一般打开方式 fopen()

fopen()函数打开文件有可能失败。如用“r”模式打开不存在的文件,此时会返回一个空指针NULL。则程序无法继续输入/输出数据。

故需要在打开文件时判断是否有错。

基本格式为

1
2
3
4
5
6
FILE *fp;
if((fp=fopen(filename,mode))==NULL)
{
printf("打开文件错误!\n"); //可加可不加
exit(1); //由exit函数终止程序运行。
}

文件的关闭 fclose()

文件打开的目的是为了读/写,当文件使用完毕后,应当关闭文件。

关闭文件的三个目的:
1.保证文件的数据不丢失,将缓冲区中的数据回写文件。
2.释放缓冲区。
3.切断文件指针与文件的关联,使关闭后的文件指针可以用于打开其他文件。

文件的读/写

字符输入/输出函数

字符输出函数fputc()

1
fputc(ch,fp);	//ch 输出的字符		fp 文件指针变量

写一个字符到fp对应文件的当前位置上

如果调用函数成功,则返回ch的值;如果失败,则返回值EOF(系统定义的宏,值为-1);

字符输入函数fgetc()

1
ch=fgetc(fp);	//fp 文件指针变量

从fp对应的当前位置读一个字符。

如果调用成功,则返回读到的字符(赋值给ch);如果读到文件结束,则返回EOF(-1)。

字符串输入/输出函数

字符串输入函数fgets()

1
fgets(str,n,fp);	//str 字符指针或数组名	n 整型量,说明输入字符串的最大长度(包括‘\0’)	fp 文件指针

从fp对应文件的当前位置,最多输入n-1个字符,在最后加‘\0’,之后放在str为首地址的内存中。

在输入过程中,如果遇到换行符或EOF,输入即结束。

函数正常调用,返回str的首地址,当出错或遇到文件结束标志时,返回NULL。

字符串输出函数fputs()

1
fputs(str,fp);	//str 函数参数str是字符指针或数组名		fp 文件指针

将首地址是str的字符串输出到fp对应文件的当前位置,自动丢弃str后的‘\0’。

函数调用成功后返回值是0,否则是EOF。

在文件使用中,可采用标准的设备文件,系统为每一个设备指定了标准的文件指针名称。

文件结束检测函数feof()

1
feof(fp);	//fp 文件指针变量

判断文件是否处于文件结束位置。