【C基础篇】之入门系统学习大全

【C基础篇】之入门系统学习大全

前言

感谢:感谢杨老师Yang-FS带我入门C语言
坦白:本人并不是该领域的专家,现在还是处于学习阶段的菜鸟,文章内容并不是本人个人编写。
作者主要工作:梳理思路,知识总结,资源搬运到本篇文章,部分内容有本人见解。
本文适应者:零基础小白,已经入门但忘记部分知识点的学者
阅读方式:细分目录,可以直接跳转到自己想学内容的那部分

1.C语言概述

1.1计算机的结构构成

  • 输入:鼠标、键盘…

  • 输出:声卡、显卡…

  • 中央处理器(CPU):

      控制器:帮助CPU获取指令交给运算器;
    
      运算器:负责数据运算;
    
      寄存器:储存cpu用来数据运算;
    
      预取器:从内存中获取程序中的指令;
    
      MMU:虚拟内存映射。
    
  • 内存储器:

      Rom(read only memory)
      PAM(随机:random access memory)
    
      优点:内存靠”电信号“来储存数据,所以不可持久化
      缺点:读写快
    
  • 硬盘

      优点:内存靠“磁信号”来储存数据,具有持久化
      缺点:读写慢
    

1.2第一个程序"Hello world"

#include <stdio.h>

// C语言中以#开头的行 称为预处理行 是在预处理阶段处理的
// #include 是包含头文件的关键字
// stdio.h 标准输入输出的头文件 我们的使用的printf就在这个头文件里

//int 函数的返回值类型 先不用管
//main 主函数 是程序的入口 每个程序必须有且仅有一个
//() 里面是main函数的参数 先不用管 ()里面的内容可以不写 但是 ()必须写
//可以写成  int main()
//	()圆括号  []方括号  {}花括号
int main(int argc, const char *argv[])
{
	//{}里面包住的部分叫做函数体,就是我们实际要执行的代码

	//printf 是系统给我们提供的输出的函数 
	//功能就是将 "" 内的内容打印到终端  \n 表示换行的意思 就是回车
	printf("hello world\n"); //C语言中每条指令结束 都需要加 分号
	return 0;//函数的返回值  先不用管
}

//单行注释

/*
	多行
	注释
*/

#if 0
	多行
	注释
#endif

1.3gcc编译器

  • 预处理阶段

    • 参数:-S
  • 生成文件:xxx.s汇编文件

  • 格式:gcc -E xxx.c -o xxx.i

  • 使用工具:预处理器(包含在gcc编译集合工具中)

  • 完成工作:

     1.文件展开(将stdio.h文件内容和源码放在一起)
     2.宏名替换成宏值
     3.注释替换成空格
     4.展开条件编辑
    
  • 编译阶段

  • 参数:-S

  • 生成文件:xxx.s汇编文件

  • 工具:编译器(包含在gcc编译集合工具中)

  • 使用命令:gcc -S xxx.i -o xxx.s

  • 完成工作:

     【重点】逐步检查语法错误----整个gcc编译四步骤中最耗时间
            将C程序翻译成汇编指令,得到.s汇编文件
    
  • 汇编阶段

  • 参数:-c

  • 生成文件:xxx.o目标文件(二进制)

  • 工具:汇编器(包含在gcc编译集合工具中)

  • 使用命令:gcc -c xxx.s -o xxx.o

  • 完成工作:

     进行汇编指令翻译成对应的二进制指令
    
  • 链接:

  • 参数:无

  • 生成文件:xxx.exe可执行文件(二进制)

  • 工具:预处理器(包含在gcc编译集合工具中)

  • 使用命令:gcc xxx.o -o xxx.exe

  • 完成工作:

      库引入
      合并多目标项目
      合并启动例程
    

2.数据类型

2.1常量与变量

2.1.1关键字

在这里插入图片描述

2.1.2数据类型

数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
在这里插入图片描述

2.1.3常量

  • 在程序运行过程中,其值不能被改变的量

  • 常量一般出现在表达式或赋值语句中

    整型常量  | 100  200 -10 0
    实型常量  | 3.14 0.125 -3 123
    字符型常量  | 'a' 'b' '1' '\n'
    字符串常量  | "abc" "123" "a"
    

2.1.4 变量

  1. 变量
    变量:
  • 在程序运行过程中,其值可以改变
  • 变量在使用前必须先定义,定义变量前必须有相应的数据类型

标识符命名规则:

  • 标识符不能是关键字 标识符只能由字母、数字、下划线组成
  • 第一个字符必须为字母或下划线 标识符中字母区分大小写

变量特点:

  • 变量在编译时为其分配相应的内存空间
  • 可以通过其名字和地址访问相应内存
  1. 声明和定义区别
  • 声明变量不需要建立存储空间,如:extern int a;
  • 定义变量需要建立存储空间,如:int b;

从广义的角度来讲声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义:

  • int b 它既是声明,同时又是定义
  • 对于 extern b来讲它只是声明不是定义

一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”

注意:常量和变量做比较时,常量放左边,如果有错,系统会报错,方便查错。

2.2整型 int

2.2.1 整型变量的定义和输出

打印格式含义
%d输出一个有符号的10进制int类型
%o输出8进制的int类型
%x输出16进制的int类型,字母以小写输出
%X输出16进制的int类型,字母以大写输出
%u输出一个10进制的无符号数

2.2.2 整型变量的输入

scanf("%d",&a);

2.2.3 short、int、long、long long

数据类型占用空间
short(短整型)2字节
int(整型)4字节
long(长整形)Windows为4字节,Linux为4字节(32位),8字节(64位)
long long(长长整形)8字节

注意:

  • 需要注意的是,整型数据在内存中占的字节数与所选择的操作系统有关。虽然 C 语言标准中没有明确规定整型数据的长度,但 long 类型整数的长度不能短于 int 类型, short 类型整数的长度不能长于 int 类型。
  • 当一个小的数据类型赋值给一个大的数据类型,不会出错,因为编译器会自动转化。但当一个大的类型赋值给一个小的数据类型,那么就可能丢失高位。
打印格式含义
%hd输出short类型
%d输出int类型
%ld输出long类型
%lld输出long long类型
%hu输出unsigned short类型
%u输出unsiged int类型
%lu输出unsigned long类型
%llu输出unsigned long long类型

2.2.4 有符号数和无符号数区别

  1. 有符号数
    有符号数是最高位为符号位,0代表正数,1代表负数。
    在计算机中,正数用二进制原码存储,负数用二进制反码存储
  2. 无符号数
    无符号数最高位不是符号位,而就是数的一部分,无符号数不可能是负数。
    当我们写程序要处理一个不可能出现负值的时候,一般用无符号数,这样可以增大数的表达最大值。
  3. 有符号和无符号整型取值范围
数据类型占用空间取值范围
short2字节-32768 到 32767 (-2^15 ~ 2 ^15-1)
int4字节-2147483648 到 2147483647 (-2^31 ~ 2 ^31-1)
long4字节-2147483648 到 2147483647 (-2^31 ~ 2 ^31-1)
unsigned short2字节0 到 65535 (0 ~ 2^16-1)
unsigned int4字节0 到 4294967295 (0 ~ 2^32-1)
unsigned long4字节0 到 4294967295 (0 ~ 2^32-1)

2.3 sizeof关键字

  • sizeof不是函数,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节
  • sizeof的返回值为size_t
  • size_t类型在32位操作系统下是unsigned int,是一个无符号的整数

2.4 字符型:char

2.4.1 字符变量的定义和输出

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

2.4.2 字符变量的输入

scanf("%c",&ch);

2.4.2 转义字符

在这里插入图片描述
注意:红色为不可打印字符

2.5实型(浮点型):float、double

  • 实型变量也可以称为浮点型变量,浮点型变量是用来存储小数数值的。在C语言中, 浮点型变量分为两种: 单精度浮点数(float)、 双精度浮点数(double), 但是double型变量所表示的浮点数比 float 型变量更精确。
  • 由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。在有效位以外的数字将被舍去,这样可能会产生一些误差。
  • 不以f结尾的常量是double类型,以f结尾的常量(如3.14f)是float类型。

2.6 进制

进制也就是进位制,是人们规定的一种进位方法。 对于任何一种进制—X进制,就表示某一位置上的数运算时是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位
在这里插入图片描述
在这里插入图片描述

2.6.1 二进制

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。

当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。
在这里插入图片描述
十进制转化二进制的方法:用十进制数除以2,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。
在这里插入图片描述
代码实现:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int input=0;
	printf("input your number>");
	scanf("%d",&input);
	int remainder=0;
	int quotient=input;
	int result=0;
	int carry=1;
    
	while(quotient!=0){
		remainder=quotient%2;//余数
		result+=remainder*carry;//存储余数
		quotient/=2;//商
		carry*=10;//让存储余数进位
	}

	printf("%d\n",result);
	return 0;
}

十进制的小数转换成二进制:小数部分和2相乘,取整数,不足1取0,每次相乘都是小数部分,顺序看取整后的数就是转化后的结果。
在这里插入图片描述

2.6.2 八进制

八进制,Octal,缩写OCT或O,一种以8为基数的计数法,采用0,1,2,3,4,5,6,7八个数字,逢八进1。一些编程语言中常常以数字0开始表明该数字是八进制。

八进制的数和二进制数可以按位对应(八进制一位对应二进制三位),因此常应用在计算机语言中。
在这里插入图片描述
十进制转化八进制的方法:
用十进制数除以8,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。
在这里插入图片描述
代码实现:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int input=0;
	printf("input your number>");
	scanf("%d",&input);
	int remainder=0;
	int quotient=input;
	int result=0;
	int carry=1;
    
	while(quotient!=0){
		remainder=quotient%8;//余数
		result+=remainder*carry;//存储余数
		quotient/=8;//商
		carry*=10;//让存储余数进位
	}

	printf("%d\n",result);
	return 0;
}

2.6.3 十六进制

十六进制(英文名称:Hexadecimal),同我们日常生活中的表示法不一样,它由0-9,A-F组成,字母不区分大小写。与10进制的对应关系是:0-9对应0-9,A-F对应10-15。

十六进制的数和二进制数可以按位对应(十六进制一位对应二进制四位),因此常应用在计算机语言中。
在这里插入图片描述
十进制转化十六进制的方法:
用十进制数除以16,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。
在这里插入图片描述

2.6.4 C语言如何表示相应进制数

进制表示
十进制以正常数字1-9开头,如123
八进制以数字0开头,如0123
十六进制以0x开头,如0x123
二进制C语言不能直接书写二进制数

2.7 计算机内存数值存储方式

2.7.1 原码

一个数的原码(原始的二进制码)有如下特点:

  • 最高位做为符号位,0表示正,为1表示负
  • 其它数值部分就是数值本身绝对值的二进制数
  • 负数的原码是在其绝对值的基础上,最高位变为1

下面数值以1字节的大小描述:

十进制数原码
+150000 1111
-151000 1111
+00000 0000
-01000 0000

原码表示法简单易懂,与带符号数本身转换方便,只要符号还原即可,但当两个正数相减或不同符号数相加时,必须比较两个数哪个绝对值大,才能决定谁减谁,才能确定结果是正还是负,所以原码不便于加减运算。

2.7.2 反码

  • 对于正数,反码与原码相同
  • 对于负数,符号位不变,其它部分取反(1变0,0变1)
十进制数反码
+150000 1111
-151111 0000
+00000 0000
- 01111 1111

反码运算也不方便,通常用来作为求补码的中间过渡

2.7.3 补码

在计算机系统中,数值一律用补码来存储。
补码特点:

  • 对于正数,原码、反码、补码相同
  • 对于负数,其补码为它的反码加1
  • 补码符号位不动,其他位求反,最后整个数加1,得到原码
十进制数补码
+150000 1111
-151111 0001
+00000 0000
-00000 0000

2.7.4 补码的意义

示例1:用8位二进制数分别表示+0和-0

十进制数原码
+00000 0000
-01000 0000
十进制数反码
+00000 0000
-01111 1111

不管以原码方式存储,还是以反码方式存储,0也有两种表示形式。为什么同样一个0有两种不同的表示方法呢?

但是如果以补码方式存储,补码统一了零的编码:

十进制数反码
+00000 0000
-01 0000 0000由于只用8位描述,最高位1丢弃,变为0000 0000

示例2:计算9-6的结果
以原码方式相加:

十进制数原码
+90000 1001
-61000 0110

在这里插入图片描述
结果为-15,不正确。

以补码方式相加:

十进制数反码
+90000 1001
-61111 1010

在这里插入图片描述
最高位的1溢出,剩余8位二进制表示的是3,正确
在计算机系统中,数值一律用补码来存储,主要原因是:

  • 统一了零的编码
  • 将符号位和其它位统一处理
  • 将减法运算转变为加法运算
  • 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃

2.7.5 数值溢出

当超过一个数据类型能够存放最大的范围时,数值会溢出。

有符号位最高位溢出的区别:符号位溢出会导致数的正负发生改变,但最高位的溢出会导致最高位丢失。

2.8 类型限定符

限定符含义
extern声明一个变量,extern声明的变量没有建立存储空间。int a;//变量在定义的时候创建存储空间
const1.只读;2.使代码更紧凑;3.编译器自然保护不希望改变的参数,防止无意修改代码(例:const int * p=&a ,表示*p不能改变a的值 )
Volatilevolatile关键字用来阻止编译器认为的无法“被代码本身”改变的代码进行优化。如在C语言中,volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
register定义寄存器变量,提高效率。register是建议型的指令,而不是命令型的指令,如果CPU有空闲寄存器,那么register就生效,如果没有空闲寄存器,那么register无效。

注意:
自动存储区:auto、register
静态存储区:const、extern、volatile

2.9字符串格式化输出和输入

2.9.1 字符串常量

  • 字符串是内存中一段连续的char空间,以’\0’(数字0)结尾。
  • 字符串常量是由双引号括起来的字符序列,如“china”、“C program”,“$12.5”等都是合法的字符串常量。

字符串常量与字符常量的不同:
在这里插入图片描述
每个字符串的结尾,编译器会自动的添加一个结束标志位’\0’,即 “a” 包含两个字符’a’和’\0’。

2.9.2 printf函数和putchar函数

printf是输出一个字符串,putchar输出一个char。

printf格式字符:

打印格式对应数据类型含义
%dint接受整数值并将它表示为有符号的十进制整数
%hdshort int短整数
%huunsigned short无符号短整数
%ounsigned int无符号8进制整数
%uunsigned int无符号10进制整数
%x,%Xunsigned int无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF
%ffloat单精度浮点数
%lfdouble双精度浮点数
%e,%Edouble科学计数法表示的数,此处"e"的大小写代表在输出时用的"e"的大小写
%cchar 字符型可以把输入的数字按照ASCII码相应转换为对应的字符
%schar *字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%pvoid *以16进制形式输出指针
%%%输出一个百分号

printf附加格式:

字符含义
l(字母l)附加在d,u,x,o前面,表示长整数
-左对齐
m(代表一个整数)数据最小宽度
0(数字0)将输出的前面补上0直到占满指定列宽为止不可以搭配使用
m.n(代表一个整数)m指域宽,即对应的输出项在输出设备上所占的字符数。n指精度,用于说明输出的实型数的小数位数。对数值型的来说,未指定n时,隐含的精度为n=6位。

2.9.3 scanf函数与getchar函数

  • getchar是从标准输入设备读取一个char。
  • scanf通过%转义的方式可以得到用户通过标准输入设备输入的数据。

3.运算符与表达式

3.1常用运算符

运算符类型作用
算术运算符用来处理四则运算
赋值运算符用于将表达式的值赋给变量
比较运算符用于表达式的比较,并返回一个真值或假值
逻辑运算符用于根据表达式的值返回真值或假值
位运算符用于处理数据的位运算
sizeof运算符用于求字节数长度

3.2 算术运算符

运算符术语示例结果
+正号+33
-负号-3-3
+10+515
-10-55
*10*550
/10/52
%取模(取余)10%31
++前自增a=2; b=++a;a=3; b=3;
++后自增a=2; b=a++;a=3; b=2;
前自减a=2; b=–a;a=1; b=1;
后自减a=2; b=a–;a=1; b=2;

3.3赋值运算符

运算符术语示例结果
=赋值a=2; b=3;a=2; b=3;
+=加等于a=0; a+=2;a=2;
-=-=a=5; a-=3;a=2;
*=乘等于a=2; a*=2;a=4;
/=除等于a=4; a/=2;a=2;
%=模等于a=3; a%2;a=1;

3.4 比较运算符

C 语言的比较运算中, “真”用数字“1”来表示, “假”用数字“0”来表示。

运算符术语示例结果
==相等于4 == 30
!=不等于4 != 31
<小于4 < 3;0
>大于4 > 31
<=小于等于4 <= 30
>=大于等于4 >= 11

3.5 逻辑运算符

运算符术语示例结果
!!a如果a为假,则!a为真;
&&a && b如果a和b都为真,则结果为真,否则为假。
||a || b如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

注意:C语言中不允许使用这样的写法 (10<x<20)
需要改写成 (x>10 && x<20)

3.6位运算符

运算符术语用法
&按位与按位运算 全1为1 有0为0
|按位或按位运算 有1为1 全0为0
^按位异或按位运算 不同为1 相同为0
~按位取反按位运算 0变1 1变0
<<按位左移整个数据向左移动 低位补0 舍弃高位
>>按位右移整个数据向右移动 高位补0 舍弃低位

3.7 运算符优先级

方便记忆12字:
单算移关与,异或逻条赋,逗号

3.7.1单(单目运算)

在这里插入图片描述

3.7.2算(算术运算)

在这里插入图片描述

3.7.3移(位移运算)

在这里插入图片描述

3.7.4关(关系运算)

在这里插入图片描述

3.7.5与(按位与)

在这里插入图片描述

3.7.6异(按位异或)

在这里插入图片描述

3.7.7或(按位或)

在这里插入图片描述

3.7.8逻(逻辑运算)

在这里插入图片描述

3.7.9条(条件运算)

在这里插入图片描述

3.7.10赋(赋值运算)

在这里插入图片描述

3.7.11逗号(逗号运算)

在这里插入图片描述

3.8类型转换

数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题。

转换的方法有两种:

  • 自动转换(隐式转换):遵循一定的规则,由编译系统自动完成。
  • 强制类型转换:把表达式的运算结果强制转换成所需的数据类型。

类型转换的原则:占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低。
在这里插入图片描述

4.程序流程结构

4.1 概述

C语言支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。

  • 顺序结构:程序按顺序执行,不发生跳转。
  • 选择结构:依据是否满足条件,有选择的执行相应功能。
  • 循环结构:依据条件是否满足,循环多次执行某段代码。

4.2 选择结构

4.2.1 if语句

在这里插入图片描述

#include <stdio.h>

int main()
{
	int a = 1;
	int b = 2;

	if (a > b)
	{
		printf("%d\n", a);
	}

	return 0;
} 

4.2.2 if…else语句

在这里插入图片描述

#include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;

	if (a > b)
	{
		printf("%d\n", a);
	}
	else
	{
		printf("%d\n", b);
	}
	return 0;
}

4.2.3 if…else if…else语句

在这里插入图片描述

#include <stdio.h>

int main()
{
	unsigned int a;
	scanf("%u", &a);

	if (a < 10)
	{
		printf("个位\n");
	}
	else if (a < 100)
	{
		printf("十位\n");
	}
	else if (a < 1000)
	{
		printf("百位\n");
	}
	else
	{
		printf("很大\n");
	}

	return 0;
}

4.2.4 三目运算符

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c;

	if (a > b)
	{
		c = a;
	}
	else
	{
		c = b;
	}
	printf("c1 = %d\n", c);

	a = 1;
	b = 2;
	c = ( a > b ? a : b );
	printf("c2 = %d\n", c);

	return 0;
}

4.2.5 switch语句

#include <stdio.h>

int main()
{
	char c;
	c = getchar();

	switch (c) //参数只能是整型变量
	{
	case '1':
		printf("OK\n");
		break;//switch遇到break就中断了
	case '2':
		printf("not OK\n");
		break;
	default://如果上面的条件都不满足,那么执行default
		printf("are u ok?\n");
	}
	return 0;
}

4.3循环结构

4.3.1 while语句

在这里插入图片描述

#include <stdio.h>

int main(void)
{
	int a = 20;
	while (a > 10)
	{
		scanf("%d", &a);
		printf("a = %d\n", a);
	}

	return 0;
}

4.3.2 do…while语句

在这里插入图片描述

#include <stdio.h>

int main(void)
{
	int a = 1;
	do
	{
		a++;
		printf("a = %d\n", a);
	} while (a < 10);

	

4.3.3 for语句

#include <stdio.h>

int main(void)
{
	int i;
	int sum = 0;
	for (i = 0; i <= 100; i++)
	{
		sum += i;
	}

	printf("sum = %d\n", sum);

	return 0;
}

4.3.4 嵌套循环

循环语句之间可以相互嵌套:

#include <stdio.h>

int main(void)
{
	int num = 0;
	int i, j, k;
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 10; j++)
		{
			for (k = 0; k < 10; k++)
			{
				printf("hello world\n");
				num++;
			}
		}
	}

	printf("num = %d\n", num);

	return 0;
}

4.3.5 goto语句

#include <stdio.h>

int main(int argc, char const *argv[])
{
    //标签放在后
    printf("11111111111111\n");
    goto NEXT;
    printf("222222222222222\n");
    NEXT:
    printf("333333333333\n");

    //标签在前
    printf("----------start-----------\n");
    int count=0;
    NEXT1:
        printf("hello world!\n");
        count++;
        if(count<5){
            goto NEXT1;
        }
        printf("--------end-------------\n");
    return 0;
}

运行结果:
在这里插入图片描述

4.4 跳转语句break、continue

4.4.1 break语句

在switch条件语句和循环语句中都可以使用break语句
作用:跳出本次循环

  • 当它出现在switch条件语句中时,作用是终止某个case并跳出switch结构。
  • 当它出现在循环语句中,作用是跳出当前内循环语句,执行后面的代码。
  • 当它出现在嵌套循环语句中,跳出最近的内循环语句,执行后面的代码。
#include <stdio.h>

int main()
{
	int i = 0;
	while (1)
	{
		i++;
		printf("i = %d\n", i);

		if (i == 10)
		{
			break; //跳出while循环
		}
	}

	int flag = 0;
	int m = 0;
	int n = 0;

	for (m = 0; m < 10; m++)
	{
		for (n = 0; n < 10; n++)
		{
			if (n == 5)
			{
				flag = 1;
				break; //跳出for (n = 0; n < 10; n++)
			}
		}

		if (flag == 1)
		{
			break; //跳出for (m = 0; m < 10; m++)
		}
	}

	return 0;
}

4.4.2 continue语句

在循环语句中,如果希望立即终止本次循环,并执行下一次循环,此时就需要使用continue语句。

#include<stdio.h>

int main()
{
	int sum = 0;           //定义变量sum

	for (int i = 1; i <= 100; i++)
	{
		if (i % 2 == 0)   //如果i是一个偶数,执行if语句中的代码
		{
			continue;      //结束本次循环
		}
		sum += i;          //实现sum和i的累加
	}

	printf("sum = %d\n", sum);

	return 0;
}

5.数组与字符串

5.1 概述

在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。

数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的。
在这里插入图片描述

5.2 一维数组

5.2.1 一维数组的定义和使用

  • 数组名字符合标识符的书写规定(数字、英文字母、下划线)
  • 数组名不能与其它变量名相同,同一作用域内是唯一的
  • 方括号[]中常量表达式表示数组元素的个数

int a[3]表示数组a有3个元素 其下标从0开始计算,因此3个元素分别为a[0],a[1],a[2]

  • 定义数组时[]内最好是常量,使用数组时[]内即可是常量,也可以是变量
#include <stdio.h>

int main()
{
	int a[10];//定义了一个数组,名字叫a,有10个成员,每个成员都是int类型
	//a[0]…… a[9],没有a[10]
	//没有a这个变量,a是数组的名字,但不是变量名,它是常量
	a[0] = 0;
	//……
	a[9] = 9;

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		a[i] = i; //给数组赋值
	}

	//遍历数组,并输出每个成员的值
	for (i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

5.2.2 一维数组的初始化

在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为零。局部数组若不初始化,内容为随机值。

int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一个数组,同时初始化所有成员变量
int a[10] = { 1, 2, 3 };//初始化前三个成员,后面所有元素都设置为0
int a[10] = { 0 };//所有的成员都设置为0
//[]中不定义元素个数,定义时必须初始化
int a[] = { 1, 2, 3, 4, 5 };//定义了一个数组,有5个成员

5.2.3 数组名

数组名是一个地址的常量,代表数组中首元素的地址。后面指针操作时,不能给数组名赋值。

#include <stdio.h>

int main()
{
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一个数组,同时初始化所有成员变量

	printf("a = %p\n", a);
	printf("&a[0] = %p\n", &a[0]);

	int n = sizeof(a); //数组占用内存的大小,10个int类型,10 * 4  = 40
	int n0 = sizeof(a[0]);//数组第0个元素占用内存大小,第0个元素为int,4

	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

5.2.4 强化训练

  1. 一维数组的最值
#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int max = a[0];
	for (i = 1; i < sizeof(a) / sizeof(a[0]); i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	printf("数组中最大值为:%d\n", max);

	return 0;
}
  1. 一维数组的逆置
#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int j = sizeof(a) / sizeof(a[0]) -1;
	int tmp;

	while (i < j)
	{
		i++;
		j--;

		tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
	}

	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}
  1. 冒泡法排序
#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int j = 0;
	int n = sizeof(a) / sizeof(a[0]);
	int tmp;

	//1、流程
	//2、试数
	for (i = 0; i < n-1; i++)
	{
		for (j = 0; j < n - i -1 ; j++)//内循环的目的是比较相邻的元素,把大的放到后面
		{
			if (a[j]  > a[j + 1])
			{
				tmp = a[j];
				a[j] = a[j+1];
				a[j+1] = tmp;
			}
		}
	}

	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

5.3二维数组

5.3.1 二维数组的定义和使用

二维数组定义的一般形式是:

类型说明符 数组名[常量表达式1][常量表达式2]

其中常量表达式1表示第一维下标的长度,常量表达式2 表示第二维下标的长度。

int a[3][4];

  • 命名规则同一维数组
  • 定义了一个三行四列的数组,数组名为a其元素类型为整型,该数组的元素个数为3×4个,即:
    在这里插入图片描述
    二维数组a是按行进行存放的,先存放a[0]行,再存放a[1]行、a[2]行,并且每行有四个元素,也是依次存放的。
  • 二维数组在概念上是二维的:其下标在两个方向上变化,对其访问一般需要两个下标。
  • 在内存中并不存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。
#include <stdio.h>

int main()
{
	//定义了一个二维数组,名字叫a
	//由3个一维数组组成,这个一维数组是int [4]
	//这3个一维数组的数组名分别为a[0],a[1],a[2]
	int a[3][4];

	a[0][0] = 0;
	//……
	a[2][3] = 12;

	//给数组每个元素赋值
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			a[i][j] = num++;
		}
	}

	//遍历数组,并输出每个成员的值
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d, ", a[i][j]);
		}
		printf("\n");
	}

	return 0;
}

5.3.2 二维数组的初始化

//分段赋值 	int a[3][4] = {{ 1, 2, 3, 4 },{ 5, 6, 7, 8, },{ 9, 10, 11, 12 }};
	int a[3][4] = 
	{ 
		{ 1, 2, 3, 4 },
		{ 5, 6, 7, 8, },
		{ 9, 10, 11, 12 }
	};

	//连续赋值
	int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };

	//可以只给部分元素赋初值,未初始化则为0
	int a[3][4] = { 1, 2, 3, 4  };

	//所有的成员都设置为0
	int a[3][4] = {0};

	//[]中不定义元素个数,定义时必须初始化
	int a[][4] = { 1, 2, 3, 4, 5, 6, 7, 8};

注意:在全局定义,未初始化值为0

在局部定义,如果初始化一部分,其他未初始化值为0

在局部定义,未初始化值为随机值

5.3.3 数组名

数组名是一个地址的常量,代表数组中首元素的地址。

#include <stdio.h>

int main()
{
	//定义了一个二维数组,名字叫a
	//二维数组是本质上还是一维数组,此一维数组有3个元素
//每个元素又是一个一维数组int[4]
	int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };

	//数组名为数组首元素地址,二维数组的第0个元素为一维数组
	//第0个一维数组的数组名为a[0]
	printf("a = %p\n", a);
	printf("a[0] = %p\n", a[0]);
	
	//测二维数组所占内存空间,有3个一维数组,每个一维数组的空间为4*4
	//sizeof(a) = 3 * 4 * 4 = 48
	printf("sizeof(a) = %d\n", sizeof(a));

	//测第0个元素所占内存空间,a[0]为第0个一维数组int[4]的数组名,4*4=16
	printf("sizeof(a[0]) = %d\n", sizeof(a[0]) );

	//测第0行0列元素所占内存空间,第0行0列元素为一个int类型,4字节
	printf("sizeof(a[0][0]) = %d\n", sizeof(a[0][0]));

	//求二维数组行数
	printf("i = %d\n", sizeof(a) / sizeof(a[0]));

	// 求二维数组列数
	printf("j = %d\n", sizeof(a[0]) / sizeof(a[0][0]));

	//求二维数组行*列总数
	printf("n = %d\n", sizeof(a) / sizeof(a[0][0]));

	return 0;
}

5.3.4 强化训练

#include <stdio.h>

int main()
{
	//二维数组:  五行、三列
	//行代表人:  老大到老五
	//列代表科目:语、数、外
	float a[5][3] = { { 80, 75, 56 }, { 59, 65, 71 }, { 59, 63, 70 }, { 85, 45, 90 }, { 76, 77, 45 } };

	int i, j, person_low[3] = { 0 };
	float s = 0, lesson_aver[3] = { 0 };

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			s = s + a[j][i];
			if (a[j][i] < 60)
			{
				person_low[i]++;
			}
		}

		lesson_aver[i] = s / 5;
		s = 0;
	}

	printf("各科的平均成绩:\n");
	for (i = 0; i < 3; i++)
	{
		printf("%.2f\n", lesson_aver[i]);
	}
		
	printf("各科不及格的人数:\n");
	for (i = 0; i < 3; i++)
	{
		printf("%d\n", person_low[i]);
	}
		
	return 0;
}

5.4 字符数组与字符串

5.4.1 字符数组与字符串区别

  • C语言中没有字符串这种数据类型,可以通过char的数组来替代;
  • 字符串一定是一个char的数组,但char的数组未必是字符串;
  • 数字0(和字符‘\0’等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char的数组。
#include <stdio.h>

int main()
{
	char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字符数组
	printf("c1 = %s\n", c1); //乱码,因为没有’\0’结束符

	//以‘\0’(‘\0’就是数字0)结尾的字符数组是字符串
	char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'}; 
	printf("c2 = %s\n", c2);

	//字符串处理以‘\0’(数字0)作为结束符,后面的'h', 'l', 'l', 'e', 'o'不会输出
	char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
	printf("c3 = %s\n", c3);

	return 0;
}

5.4.2 字符串的初始化

#include <stdio.h>

// C语言没有字符串类型,通过字符数组模拟
// C语言字符串,以字符‘\0’, 数字0
int main()
{
	//不指定长度, 没有0结束符,有多少个元素就有多长
	char buf[] = { 'a', 'b', 'c' };
	printf("buf = %s\n", buf);	//乱码

	//指定长度,后面没有赋值的元素,自动补0
	char buf2[100] = { 'a', 'b', 'c' };
char buf[1000]={“hello”};
	printf("buf2 = %s\n", buf2);

	//所有元素赋值为0
	char buf3[100] = { 0 };

	//char buf4[2] = { '1', '2', '3' };//数组越界

	char buf5[50] = { '1', 'a', 'b', '0', '7' };
	printf("buf5 = %s\n", buf5);

	char buf6[50] = { '1', 'a', 'b', 0, '7' };
	printf("buf6 = %s\n", buf6);

	char buf7[50] = { '1', 'a', 'b', '\0', '7' };
	printf("buf7 = %s\n", buf7);

	//使用字符串初始化,编译器自动在后面补0,常用
	char buf8[] = "agjdslgjlsdjg";

	//'\0'后面最好不要连着数字,有可能几个数字连起来刚好是一个转义字符
	//'\ddd'八进制字义字符,'\xdd'十六进制转移字符
	// \012相当于\n
	char str[] = "\012abc";
	printf("str == %s\n", str);

	return 0;
}

5.4.3 字符串的输入输出

由于字符串采用了’\0’标志,字符串的输入输出将变得简单方便。

  1. gets()

#include <stdio.h>
char *gets(char *s);
功能:从标准输入读入字符,并保存到s指定的内存空间,直到出现换行符或读到文件结尾为止。
参数:
s:字符串首地址
返回值:
成功:读入的字符串
失败:NULL

gets(str)与scanf(“%s”,str)的区别:

  • gets(str)允许输入的字符串含有空格
  • scanf(“%s”,str)不允许含有空格

注意:由于scanf()和gets()无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。

  1. fgets()

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 ‘\0’ 作为字符串结束。
参数:
s:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针,如果读键盘输入的字符串,固定写为stdin
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL

fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。fgets()函数是安全的,不存在缓冲区溢出的问题。

  1. puts()

#include <stdio.h>
int puts(const char *s);
功能:标准设备输出s字符串,在输出完成后自动输出一个’\n’。
参数:
s:字符串首地址
返回值:
成功:非负数 0
失败:-1

  1. fputs()

#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 ‘\0’ 不写入文件。
参数:
str:字符串
stream:文件指针,如果把字符串输出到屏幕,固定写为stdout
返回值:
成功:0
失败:-1

fputs()是puts()的文件操作版本,但fputs()不会自动输出一个’\n’。

5.4.4计算字符串长度

#include <string.h>
size_t strlen(const char *s);
功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’
参数:
s:字符串首地址
返回值:字符串s的长度,size_t为unsigned int类型

#include <string.h>
size_t strlen(const char *s);
功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’
参数:
s:字符串首地址
返回值:字符串s的长度,size_tunsigned int类型

6.函数

6.1 概述

6.1.1 函数分类

C 程序是由函数组成的,我们写的代码都是由主函数 main()开始执行的。函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。

从函数定义的角度看,函数可分为系统函数和用户定义函数两种:

  • 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
  • 用户定义函数:用以解决用户的专门需要。

6.1.2 函数的作用

  • 函数的使用可以省去重复代码的编写,降低代码重复率
// 求两数的最大值
int max(int a, int b)
{
	if (a > b){
		return a;
	}
	else{
		return b;
	}
}

int main()
{
	// 操作1 ……
	// ……
	int a1 = 10, b1 = 20, c1 = 0;
	c1 = max(a1, b1); // 调用max()

	// 操作2 ……
	// ……
	int a2 = 11, b2 = 21, c2 = 0;
	c2 = max(a2, b2); // 调用max()

	// ……

	return 0;
}
  • 函数可以让程序更加模块化,从而有利于程序的阅读,修改和完善

可以这样理解:程序就像公司,公司是由部门组成的,这个部门就类似于C程序的函数。

6.1.3 函数的调用:产生随机数

当调用函数时,需要关心5要素:

  1. 头文件:包含指定的头文件
  2. 函数名字:函数名字必须和头文件声明的名字一样
  3. 功能:需要知道此函数能干嘛后才调用
  4. 参数:参数类型要匹配
  5. 返回值:根据需要接收返回值
#include <time.h>
time_t time(time_t *t);
功能:获取当前系统时间
参数:常设置为NULL
返回值:当前系统时间, time_t 相当于long类型,单位为毫秒

#include <stdlib.h>
void srand(unsigned int seed);
功能:用来设置rand()产生随机数时的随机种子
参数:如果每次seed相等,rand()产生随机数相等
返回值:无

#include <stdlib.h>
int rand(void);
功能:返回一个随机数值
参数:无
返回值:随机数

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main()
{
	time_t tm = time(NULL);//得到系统时间
	srand((unsigned int)tm);//随机种子只需要设置一次即可

	int r = rand();
	printf("r = %d\n", r);

	return 0;
}

6.2 函数定义

6.2.1函数定义格式

函数定义的一般形式:

返回类型 函数名(形式参数列表)
{
数据定义部分;
执行语句部分;
}

在这里插入图片描述

6.2.2 函数名字、形参、函数体、返回值

  1. 函数名
    理论上是可以随意起名字,最好起的名字见名知意,应该让用户看到这个函数名字就知道这个函数的功能。注意,函数名的后面有个圆换号(),代表这个为函数,不是普通的变量名。

  2. 形参列表
    在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据,所以,形参里的变量不能赋值。

void max(int a = 10, int b = 20) // error, 形参不能赋值
{
}

在定义函数时指定的形参,必须是,类型+变量的形式:

//1: right, 类型+变量
void max(int a, int b)
{
}

//2: error, 只有类型,没有变量
void max(int, int)
{
}

//3: error, 只有变量,没有类型
int a, int b;
void max(a, b)
{
}

在定义函数时指定的形参,可有可无,根据函数的需要来设计,如果没有形参,圆括号内容为空,或写一个void关键字:

// 没形参, 圆括号内容为空
void max()
{
}

// 没形参, 圆括号内容为void关键字
void max(void)
{
}
  1. 函数体
    花括号{ }里的内容即为函数体的内容,这里为函数功能实现的过程,这和以前的写代码没太大区别,以前我们把代码写在main()函数里,现在只是把这些写到别的函数里。

  2. 返回值
    函数的返回值是通过函数中的return语句获得的,return后面的值也可以是一个表达式。

a)尽量保证return语句中表达式的值和函数返回类型是同一类型。

int max() // 函数的返回值为int类型
{
	int a = 10;
	return a;// 返回值a为int类型,函数返回类型也是int,匹配
}

b)如果函数返回的类型和return语句中表达式的值不一致,则以函数返回类型为准,即函数返回类型决定返回值的类型。对数值型数据,可以自动进行类型转换。

double max() // 函数的返回值为double类型
{
	int a = 10;
	return a;// 返回值a为int类型,它会转为double类型再返回
}

注意:如果函数返回的类型和return语句中表达式的值不一致,而它又无法自动进行类型转换,程序则会报错。

c)return语句的另一个作用为中断return所在的执行函数,类似于break中断循环、switch语句一样。

int max()
{
	return 1;// 执行到,函数已经被中断,所以下面的return 2无法被执行到
	return 2;// 没有执行
}

d)如果函数带返回值,return后面必须跟着一个值,如果函数没有返回值,函数名字的前面必须写一个void关键字,这时候,我们写代码时也可以通过return中断函数(也可以不用),只是这时,return后面不带内容( 分号“;”除外)。

void max()// 最好要有void关键字
{
	return; // 中断函数,这个可有可无
}

6.3 函数的调用

定义函数后,我们需要调用此函数才能执行到这个函数里的代码段。这和main()函数不一样,main()为编译器设定好自动调用的主函数,无需人为调用,我们都是在main()函数里调用别的函数,一个 C 程序里有且只有一个main()函数。

6.3.1 函数执行流程

#include <stdio.h>

void print_test()
{
	printf("this is for test\n");
}

int main()
{
	print_test();	// print_test函数的调用

	return 0;
}

1)进入main()函数
2)调用print_test()函数:
a.它会在main()函数的前寻找有没有一个名字叫“print_test”的函数定义;
b.如果找到,接着检查函数的参数,这里调用函数时没有传参,函数定义也没有形参,参数类型匹配;
c.开始执行print_test()函数,这时候,main()函数里面的执行会阻塞( 停 )在print_test()这一行代码,等待print_test()函数的执行。
3)print_test()函数执行完( 这里打印一句话 ),main()才会继续往下执行,执行到return 0, 程序执行完毕。

6.3.2 函数的形参和实参

  • 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
  • 实参出现在主调函数中,进入被调函数后,实参也不能使用。
  • 实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参。
  • 在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放。
  • 实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值。

6.3.3 无参函数调用

如果是调用无参函数,则不能加上“实参”,但括号不能省略。

// 函数的定义
void test()
{
}

int main()
{
	// 函数的调用
	test();	// right, 圆括号()不能省略
	test(250); // error, 函数定义时没有参数

return 0;
}

6.3.4 有参函数调用

a)如果实参表列包含多个实参,则各参数间用逗号隔开。

// 函数的定义
void test(int a, int b)
{
}

int main()
{
	int p = 10, q = 20;
	test(p, q);	// 函数的调用

	return 0;
}

b)实参与形参的个数应相等,类型应匹配(相同或赋值兼容)。实参与形参按顺序对应,一对一地传递数据。

c)实参可以是常量、变量或表达式,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。所以,这里的变量是在圆括号( )外面定义好、赋好值的变量。

// 函数的定义
void test(int a, int b)
{
}

int main()
{
	// 函数的调用
	int p = 10, q = 20;
	test(p, q);	// right
	test(11, 30 - 10); // right

	test(int a, int b); // error, 不应该在圆括号里定义变量

	return 0;
}

6.3.5 函数返回值

a)如果函数定义没有返回值,函数调用时不能写void关键字,调用函数时也不能接收函数的返回值。

// 函数的定义
void test()
{
}

int main()
{
	// 函数的调用
	test(); // right
	void test(); // error, void关键字只能出现在定义,不可能出现在调用的地方
	int a = test();	// error, 函数定义根本就没有返回值

	return 0;
}

b)如果函数定义有返回值,这个返回值我们根据用户需要可用可不用,但是,假如我们需要使用这个函数返回值,我们需要定义一个匹配类型的变量来接收。

// 函数的定义, 返回值为int类型
int test()
{
}

int main()
{
	// 函数的调用
	int a = test(); // right, a为int类型
	int b;
	b = test();	// right, 和上面等级

	char *p = test(); // 虽然调用成功没有意义, p为char *, 函数返回值为int, 类型不匹配

	// error, 必须定义一个匹配类型的变量来接收返回值
	// int只是类型,没有定义变量
	int = test();	
	
	// error, 必须定义一个匹配类型的变量来接收返回值
	// int只是类型,没有定义变量
	int test();
	
	return 0;
}

6.4 函数的声明

如果使用用户自己定义的函数,而该函数与调用它的函数(即主调函数)不在同一文件中,或者函数定义的位置在主调函数之后,则必须在调用此函数之前对被调用的函数作声明。

所谓函数声明,就是在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,相当于告诉编译器,函数在后面定义,以便使编译能正常进行。

注意:一个函数只能被定义一次,但可以声明多次。

#include <stdio.h>

int max(int x, int y); // 函数的声明,分号不能省略
// int max(int, int); // 另一种方式

int main()
{
	int a = 10, b = 25, num_max = 0;
	num_max = max(a, b); // 函数的调用

	printf("num_max = %d\n", num_max);

	return 0;
}

// 函数的定义
int max(int x, int y)
{
	return x > y ? x : y;
}

函数定义和声明的区别:
1)定义是指对函数功能的确立,包括指定函数名、函数类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。
2)声明的作用则是把函数的名字、函数类型以及形参的个数、类型和顺序(注意,不包括函数体)通知编译系统,以便在对包含函数调用的语句进行编译时,据此对其进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致)。

6.5 main函数与exit函数

在main函数中调用exit和return结果是一样的,但在子函数中调用return只是代表子函数终止了,在子函数中调用exit,那么程序终止。

#include <stdio.h>
#include <stdlib.h>

void fun()
{
	printf("fun\n");
	//return;
	exit(0);
}

int main()
{
	fun();
	while (1);

	return 0;
}

7.指针

7.1 概述

7.1.1 内存

内存含义:

  • 存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。
  • 内存:内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3。
  • 外存:外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘。

内存是沟通CPU与硬盘的桥梁:

  • 暂存放CPU中的运算数据
  • 暂存与硬盘等外部存储器交换的数据
大小端存储问题(字节序问题)

小端存储:地址低位存储数据低位,地址高位存储数据高位

大端存储:地址高位存储数据低位,地址低位存储数据高位
例1:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int num=0x41424344;
	printf("%s\n",num);
	return 0;
}

结果:DCBA+不确定的值
例2:判断当前系统的大小端问题:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a[]={1,2};
	int *p=(int *)&a;
	if(p>p+1){
		printf("大端存储\n");
	}else{
		printf("小端存储\n");
	}
	return 0;
}

7.1.2 物理存储器和存储地址空间

有关内存的两个概念:物理存储器和存储地址空间。

物理存储器:实际存在的具体存储器芯片。

  • 主板上装插的内存条
  • 显示卡上的显示RAM芯片
  • 各种适配卡上的RAM芯片和ROM芯片

存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义。

  • 编码:对每个物理存储单元(一个字节)分配一个号码
  • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写

7.1.3 内存地址

  • 将内存抽象成一个很大的一维字符数组。
  • 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
  • 这个内存编号我们称之为内存地址。
    内存中的每一个数据都会分配相应的地址:
  • char:占一个字节分配一个地址
  • int: 占四个字节分配四个地址
  • float、struct、函数、数组等
    在这里插入图片描述

7.1.4 指针和指针变量

  • 内存区的每一个字节都有一个编号,这就是“地址”。
  • 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
  • 指针的实质就是内存“地址”。指针就是地址,地址就是指针。
  • 指针是内存单元的编号,指针变量是存放地址的变量。
    通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

7.2 指针基础知识

7.2.1 指针变量的定义和使用

  • List item指针也是一种数据类型,指针变量也是一种变量
  • 指针变量指向谁,就把谁的地址赋值给指针变量
  • “*”操作符操作的是指针变量指向的内存空间
#include <stdio.h>

int main()
{
	int a = 0;
	char b = 100;
	printf("%p, %p\n", &a, &b); //打印a, b的地址

	//int *代表是一种数据类型,int*指针类型,p才是变量名
	//定义了一个指针类型的变量,可以指向一个int类型变量的地址
	int *p;
	p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
	printf("%d\n", *p);//p指向了a的地址,*p就是a的值

	char *p1 = &b;
	printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值

	return 0;
}

注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

7.2.2 通过指针间接修改变量的值

int a = 0;
int b = 11;
int *p = &a;

*p = 100;
printf("a = %d, *p = %d\n", a, *p);

p = &b;
*p = 22;
printf("b = %d, *p = %d\n", b, *p);

7.2.3 指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8
  • sizeof()测的是指针变量指向存储地址的大小
  • 在32位平台,所有的指针(地址)都是32位(4字节)
  • 在64位平台,所有的指针(地址)都是64位(8字节)
int *p1;
int **p2;
char *p3;
char **p4;
printf("sizeof(p1) = %d\n", sizeof(p1));
printf("sizeof(p2) = %d\n", sizeof(p2));
printf("sizeof(p3) = %d\n", sizeof(p3));
printf("sizeof(p4) = %d\n", sizeof(p4));
printf("sizeof(double *) = %d\n", sizeof(double *));

7.2.4 野指针和空指针

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。

int a = 100;
int *p;
p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义

p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义

*p = 1000;  //操作野指针指向未知区域,内存出问题,err

但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

int *p = NULL;

NULL是一个值为0的宏常量:

#define NULL    ((void *)0)

7.2.5万能指针void *

void *指针可以指向任意变量的内存空间:

void *p = NULL;

int a = 10;
p = (void *)&a; //指向变量时,最好转换为void *

//使用指针变量指向的内存时,转换为int *
*( (int *)p ) = 11;
printf("a = %d\n", a);

7.2.6 const修饰的指针变量

int a = 100;
int b = 200;

//指向常量的指针
//修饰*,指针指向内存区域不能修改,指针指向可以变
const int * p1 = &a; //等价于int const *p1 = &a;
//*p1 = 111; //err
p1 = &b; //ok

//指针常量
//修饰p1,指针指向不能变,指针指向的内存可以修改
int * const p2 = &a;
//p2 = &b; //err
*p2 = 222; //ok

在编辑程序时,指针作为函数参数,如果不想修改指针对应内存空间的值,需要使用const修饰指针数据类型。

7.3 指针和数组

7.3.1 数组名

数组名字是数组的首元素地址,但它是一个地址常量:

int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
	printf("a = %p\n", a);
	printf("&a[0] = %p\n", &a[0]);

	//a = 10; //err, 数组名只是常量,不能修改

7.3.2 指针操作数组元素

#include <stdio.h>

int  main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);
	
	for (i = 0; i < n; i++)
	{
		//printf("%d, ", a[i]);
		printf("%d, ", *(a+i));
	}
	printf("\n");

	int *p = a; 	//定义一个指针变量保存a的地址
	for (i = 0; i < n; i++)
	{
		p[i] = 2 * i;
	}

	for (i = 0; i < n; i++)
	{
		printf("%d, ", *(p + i));
	}
	printf("\n");


	return 0;
}
7.3.2.1指针和二维数组之间的关系
s[i] [j]  ==* (*(s+i)+j) == * (s[i]+j)

7.3.3 指针加减运算

1)加法运算

  • 指针计算不是简单的整数相加
  • 如果是一个int *,+1的结果是增加一个int的大小
  • 如果是一个char *,+1的结果是增加一个char大小
#include <stdio.h>

int main()
{
	int a;
	int *p = &a;
	printf("%d\n", p);
	p += 2;//移动了2个int
	printf("%d\n", p);

	char b = 0;
	char *p1 = &b;
	printf("%d\n", p1);
	p1 += 2;//移动了2个char
	printf("%d\n", p1);

	return 0;
}

通过改变指针指向操作数组元素:

#include <stdio.h>

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);

	int *p = a;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", *p);
		p++;
	}
	printf("\n");
	
	return 0;
}

2)减法运算
示例1:

#include <stdio.h>

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);

	int *p = a+n-1;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", *p);
		p--;
	}
	printf("\n");

	return 0;
}

示例2:

#include <stdio.h>

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int *p2 = &a[2]; //第2个元素地址
	int *p1 = &a[1]; //第1个元素地址
	printf("p1 = %p, p2 = %p\n", p1, p2);

	int n1 = p2 - p1; //n1 = 1
	int n2 = (int)p2 - (int)p1; //n2 = 4
	printf("n1 = %d, n2 = %d\n", n1, n2);
	
	return 0;
}

7.3.4 指针数组

指针数组,它是数组,数组的每个元素都是指针类型。

#include <stdio.h>

int main()
{
	//指针数组
	int *p[3];
	int a = 1;
	int b = 2;
	int c = 3;
	int i = 0;

	p[0] = &a;
	p[1] = &b;
	p[2] = &c;

	for (i = 0; i < sizeof(p) / sizeof(p[0]); i++ )
	{
		printf("%d, ", *(p[i]));
	}
	printf("\n");
	
	return 0;
}

7.4 多级指针

  • C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
  • 二级指针就是指向一个一级指针变量地址的指针。
  • 三级指针基本用不着,但考试会考。
int a = 10;
int *p = &a; //一级指针
*p = 100; //*p就是a

int **q = &p;
//*q就是p
//**q就是a

int ***t = &q;
//*t就是q
//**t就是p
//***t就是a

7.5 指针和函数

7.5.1 函数形参改变实参的值

#include <stdio.h>

void swap1(int x, int y)
{
	int tmp;
	tmp = x;
	x = y;
	y = tmp;
	printf("x = %d, y = %d\n", x, y);
}

void swap2(int *x, int *y)
{
	int tmp;
	tmp = *x;
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 3;
	int b = 5;
	swap1(a, b); //值传递
	printf("a = %d, b = %d\n", a, b);

	a = 3;
	b = 5;
	swap2(&a, &b); //地址传递
	printf("a2 = %d, b2 = %d\n", a, b);

	return 0;
}

7.5.2 数组名做函数参数

数组名做函数参数,函数的形参会退化为指针:

#include <stdio.h>

void printArrary(int *a, int n)	
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", a[i]);
	}
	printf("\n");
}

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int n = sizeof(a) / sizeof(a[0]);

	//数组名做函数参数
	printArrary(a, n); 
	return 0;
}

7.5.3 指针做为函数的返回值

#include <stdio.h>

int a = 10;

int *getA()
{
	return &a;
}


int main()
{
	*( getA() ) = 111;
	printf("a = %d\n", a);

	return 0;
}

7.6 指针和字符串

7.6.1 字符指针

#include <stdio.h>

int main()
{
	char str[] = "hello world";
	char *p = str;
	*p = 'm';
	p++;
	*p = 'i';
	printf("%s\n", str);

	p = "mike jiang";
	printf("%s\n", p);

	char *q = "test";
	printf("%s\n", q);

	return 0;
}

7.6.2 字符指针做函数参数

#include <stdio.h>

void mystrcat(char *dest, const char *src)
{
	int len1 = 0;
	int len2 = 0;
	while (dest[len1])
	{
		len1++;
	}
	while (src[len2])
	{
		len2++;
	}

	int i;
	for (i = 0; i < len2; i++)
	{
		dest[len1 + i] = src[i];
	}
}

int main()
{
	char dst[100] = "hello mike";
	char src[] = "123456";
	
	mystrcat(dst, src);
	printf("dst = %s\n", dst);

	return 0;
}

7.6.3 const修饰的指针变量

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	//const修饰一个变量为只读
	const int a = 10;
	//a = 100; //err

	//指针变量, 指针指向的内存, 2个不同概念
	char buf[] = "aklgjdlsgjlkds";

	//从左往右看,跳过类型,看修饰哪个字符
	//如果是*, 说明指针指向的内存不能改变
	//如果是指针变量,说明指针的指向不能改变,指针的值不能修改
	const char *p = buf;
	// 等价于上面 char const *p1 = buf;
	//p[1] = '2'; //err
	p = "agdlsjaglkdsajgl"; //ok

	char * const p2 = buf;
	p2[1] = '3';
	//p2 = "salkjgldsjaglk"; //err

	//p3为只读,指向不能变,指向的内存也不能变
	const char * const p3 = buf;

	return 0;
}

7.6.4 指针数组做为main函数的形参

int main(int argc, char *argv[]);
  • main函数是操作系统调用的,第一个参数标明argc数组的成员数量,argv数组的每个成员都是char *类型
  • argv是命令行参数的字符串数组
  • argc代表命令行参数的数量,程序名字本身算一个参数
#include <stdio.h>
//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, const char *argv[])
{
	//argc是传参个数,比如:./a.out是1个参数
	printf("%d\n",argc);
	
	//argv是获取命令行的参数,是一个指针数组
	int i=0;
	while(argv[i])
		printf("%s\n",argv[i++]);
	
	return 0;
}

7.6.5 项目开发常用字符串应用模型

  1. strstr中的while和do-while模型
    利用strstr标准库函数找出一个字符串中substr出现的个数。
    a)while模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	char *p = "11abcd111122abcd333abcd3322abcd3333322qqq";
	int n = 0;

	while ((p = strstr(p, "abcd")) != NULL)
	{
		//能进来,肯定有匹配的子串
		//重新设置起点位置
		p = p + strlen("abcd");
		n++;

		if (*p == 0) //如果到结束符
		{
			break;
		}
	}
	printf("n = %d\n", n);
	return 0;
}

b)do-while模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	char *p = "11abcd111122abcd333abcd3322abcd3333322qqq";
	int n = 0;

	do
	{
		p = strstr(p, "abcd");
		if (p != NULL)
		{
			n++; //累计个数

			//重新设置查找的起点
			p = p + strlen("abcd");

		}
		else //如果没有匹配的字符串,跳出循环
		{
			break;
		}
	} while (*p != 0); //如果没有到结尾

	printf("n = %d\n", n);
	return 0;
}
  1. 两头堵模型
    求非空字符串元素的个数:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int fun(char *p, int *n)
{
	if (p == NULL || n == NULL)
	{
		return -1;
	}

	int begin = 0;
	int end = strlen(p) - 1;

	//从左边开始
	//如果当前字符为空,而且没有结束
	while (p[begin] == ' ' && p[begin] != 0)
	{
		begin++; //位置从右移动一位
	}

	//从右往左移动
	while (p[end] == ' ' && end > 0)
	{
		end--; //往左移动
	}

	if (end == 0)
	{
		return -2;
	}

	//非空元素个数
	*n = end - begin + 1;

	return 0;
}

int main(void)
{
	char *p = "      abcddsgadsgefg      ";
	int ret = 0;
	int n = 0;

	ret = fun(p, &n);
	if (ret != 0)
	{
		return ret;
	}
	printf("非空字符串元素个数:%d\n", n);

	return 0;
}
  1. 字符串反转模型(逆置)
    在这里插入图片描述
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int inverse(char *p)
{
	if (p == NULL)
	{
		return -1;
	}
	char *str = p;
	int begin = 0;
	int end = strlen(str) - 1;
	char tmp;

	while (begin < end)
	{
		//交换元素
		tmp = str[begin];
		str[begin] = str[end];
		str[end] = tmp;

		begin++;  //往右移动位置
		end--;	    //往左移动位置
	}

	return 0;
}

int main(void)
{
	//char *str = "abcdefg"; //文件常量区,内容不允许修改
	char str[] = "abcdef";

	int ret = inverse(str);
	if (ret != 0)
	{
		return ret;
	}

	printf("str ========== %s\n", str);
	return 0;
}

7.6.6 字符串处理函数

  1. strcpy()

#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串复制到dest所指向的空间中,'\0’也会拷贝过去
参数:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失败:NULL

注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。

char dest[20] = "123456789";
char src[] = "hello world";
strcpy(dest, src);
printf("%s\n", dest);

实现strcpy()函数:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	char s1[32];
	char s2[32];
	puts("请输入第一个字符串>");
	gets(s1);

	puts("请输入二个字符串>");
	gets(s2);

	int *p1=s1,*p2=s2,i=0;
#if 0
	while(*(p2+i)!='\0'){
		*(p1+i)=*(p2+i);
		i++;
	}
	*(p1+i)='\0';
#endif
	while(*p2){
		*p1=*p2;
		p1++;
		p2++;
	}
	*p1='\0'
	printf("%s\n",s1);
	return 0;
}
  1. strncpy()

#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含’\0’。
参数:
dest:目的字符串首地址
src:源字符首地址
n:指定需要拷贝字符串个数
返回值:
成功:返回dest字符串的首地址
失败:NULL

char dest[20] ;
char src[] = "hello world";

strncpy(dest, src, 5);
printf("%s\n", dest);

dest[5] = '\0';
printf("%s\n", dest);
  1. strcat()

#include <string.h>
char *strcat(char *dest, const char *src);
功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
参数:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失败:NULL

char str[20] = "123";
char *src = "hello world";
printf("%s\n", strcat(str, src));

封装函数,实现strcat()函数:

#include <stdio.h>

char *my_strcat(char *s1,char *s2){
	char *s=s1;

	while(*s++);
	s--;

	while(*s2)*s++=*s2++;
	*s='\0';

	return s1;
}

int main(int argc, const char *argv[])
{
	printf("input two character string>\n");
	char str1[32]="";
	char str2[32]="";
	gets(str1);
	gets(str2);
	printf("%s\n",my_strcat(str1,str2));

	return 0;
}
  1. strncat()

#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
参数:
dest:目的字符串首地址
src:源字符首地址
n:指定需要追加字符串个数
返回值:
成功:返回dest字符串的首地址
失败:NULL

char str[20] = "123";
char *src = "hello world";
printf("%s\n", strncat(str, src, 5));
  1. strcmp()

#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
参数:
s1:字符串1首地址
s2:字符串2首地址
返回值:
相等:0
大于:>0 在不同操作系统strcmp结果会不同 返回ASCII差值
小于:<0

char *str1 = "hello world";
char *str2 = "hello mike";

if (strcmp(str1, str2) == 0)
{
	printf("str1==str2\n");
}
else if (strcmp(str1, str2) > 0)
{
	printf("str1>str2\n");
}	
else
{
	printf("str1<str2\n");
}

封装函数,实现strcmp()函数:

#include <stdio.h>

int my_strcmp(char *s1,char *s2){
	while(*s1&&*s2){
		if(*s1!=*s2)break;
		s1++;
		s2++;
	}
	return *s1-*s2;
}

int main(int argc, const char *argv[])
{
	printf("input two character string>\n");
	char str1[32]="";
	char str2[32]="";
	gets(str1);
	gets(str2);
	printf("%d\n",my_strcmp(str1,str2));
	
	return 0;
}

  1. strncmp()

#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小。
参数:
s1:字符串1首地址
s2:字符串2首地址
n:指定比较字符串的数量
返回值:
相等:0
大于: > 0
小于: < 0

char *str1 = "hello world";
char *str2 = "hello mike";

if (strncmp(str1, str2, 5) == 0)
{
	printf("str1==str2\n");
}
else if (strcmp(str1, "hello world") > 0)
{
	printf("str1>str2\n");
}
else
{
	printf("str1<str2\n");
}
  1. sprintf()

#include <stdio.h>
int sprintf(char *str, const char *format, …);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 ‘\0’ 为止。
参数:
str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:
成功:实际格式化的字符个数
失败: - 1

char dst[100] = { 0 };
	int a = 10;
	char src[] = "hello world";
	printf("a = %d, src = %s", a, src);
	printf("\n");

	int len = sprintf(dst, "a = %d, src = %s", a, src);
	printf("dst = \" %s\"\n", dst);
	printf("len = %d\n", len);
  1. sscanf()

#include <stdio.h>
int sscanf(const char *str, const char *format, …);
功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:
成功:参数数目,成功转换的值的个数
失败: - 1

char src[] = "a=10, b=20";
int a;
int b;
sscanf(src, "a=%d,  b=%d", &a, &b);
printf("a:%d, b:%d\n", a, b);
  1. strchr()

#include <string.h>
char *strchr(const char *s, int c);
功能:在字符串s中查找字母c出现的位置
参数:
s:字符串首地址
c:匹配字母(字符)
返回值:
成功:返回第一次出现的c地址
失败:NULL

char src[] = "ddda123abcd";
char *p = strchr(src, 'a');
printf("p = %s\n", p);
  1. strstr()

#include <string.h>
char *strstr(const char *haystack, const char *needle);
功能:在字符串haystack中查找字符串needle出现的位置
参数:
haystack:源字符串首地址
needle:匹配字符串首地址
返回值:
成功:返回第一次出现的needle地址
失败:NULL

char src[] = "ddddabcd123abcd333abcd";
char *p = strstr(src, "abcd");
printf("p = %s\n", p);
  1. strtok()

#include <string.h>
char *strtok(char *str, const char *delim);
功能:来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0。
参数:
str:指向欲分割的字符串
delim:为分割字符串中包含的所有字符
返回值:
成功:分割后字符串首地址
失败:NULL

  • 在第一次调用时:strtok()必需给予参数s字符串
  • 往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针
char a[100] = "adc*fvcv.ebcy*hghbdfg$casdert";
char *s = strtok(a, ".*$");//将"*"分割的子串取出
while (s != NULL)
{
	printf("%s\n", s);
	s = strtok(NULL, "*");
}
  1. atoi()

#include <stdlib.h>
int atoi(const char *nptr);
功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符(‘\0’)才结束转换,并将结果返回返回值。
参数:
nptr:待转换的字符串
返回值:成功转换后整数

类似的函数有:

  • atof():把一个小数形式的字符串转化为一个浮点数。
  • atol():将一个字符串转化为long类型
char str1[] = "          -10";
int num1 = atoi(str1);
printf("num1 = %d\n", num1);

char str2[] = "0.123";
double num2 = atof(str2);
printf("num2 = %lf\n", num2);

char str3[] = "123L";
long num3 = atol(str3);
printf("num3 = %ld\n", num3);

7.7 指针小结

定义说明
int i定义整形变量
int *p定义一个指向int的指针变量
int a[10]定义一个有10个元素的数组,每个元素类型为int
int *p[10]定义一个有10个元素的数组,每个元素类型为int*
int func()定义一个函数,返回值为int型
int *func()定义一个函数,返回值为int *型
int **p定义一个指向int的指针的指针,二级指针

写在最后

C语言的核心,就是指针,希望看过此篇文章的朋友们,受益多多,指针可以应用到“指天指地指空气”炉火纯青的地步。

码字不易,记得点赞鼓励一下哦~

日志

完结日期:2022-10-16

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程远泊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值