C语言记录详解版

学习参考:b站翁恺老师

还有其他文章

目录

01初识C语言

1.1语言

什么是C语言?

计算机语言是什么?

1.2语言的发展

1.3计算机的思维方式

 1.4程序的执行

1.5计算机如何解决问题

1.6算法

1.7C语言编程软件

​编辑

02第一个C语言程序

devc++

vs2022

2.1程序框架

主函数定义的格式:

其它函数定义的格式:

输出(printf函数)

程序中的错误

main函数注意点及其他写法

2.2C语言程序组成

2.3做点计算

2.4注释

注释的分类

注释的注意点

注释的应用场景

使用注释的好处

03变量

3.1变量定义

算找零(如何能在程序运行时输入数字23,然后计算输入结果?)

如何输入

变量

 怎么定义变量

如何修改变量值?

变量之间的传递

如何查看变量的值?

3.2C语言的关键字

什么是关键字?

关键字分类

​编辑

 3.3标识符

什么是标识符?

标识符命名规则

 3.4赋值和初始化

赋值

​编辑​编辑

 初始化

3.5变量类型

3.6程序输入(scanf函数)

 3.7 常量

 const

常量的类型

3.8浮点数

3.9局部变量和全局变量

3.10变量的作用域和生命周期

3.11putchar和getchar

 04表达式

4.1运算符

运算符分类

运算符的优先级和结合性

 ​编辑​编辑

 算数运算符

赋值运算符

递增递减运算符

sizeof运算符

逗号运算符

关系运算符

逻辑运算符

4.2交换

05判断和分支(if-else)

5.1流程控制基本概念

顺序结构

选择结构

循环结构

5.2选择结构-if语句

if第一种形式

 if第二种形式

if第三种形式

if嵌套

5.3选择结构-switch

switch语句

switch注意点

06循环语句

6.1while循环

6.2 do-while循环

 6.3循环应用1

猜数游戏

 算平均数

6.4第三种循环-for循环

6.5循环的计算和选择

 三种循环相比

while和do while应用场景

 6.6循环控制(四大跳转)

break

continue

goto

 return

 如何用break和continue来控制循环

6.7循环的嵌套

07数据类型

7.1数据

什么是数据?

数据分类

选择整数类型

类型有何不同

 7.2字节

7.3类型转换

自动类型转换

强制类型转换

7.4逃逸字符

7.5bool

08函数

8.1函数的基本概念

8.2函数的分类

8.3函数的定义

8.4函数的参数和返回值

形式参数

实际参数

形参、实参注意点

返回值类型注意点

8.5递归函数

09进制

9.1进制的基本概念

9.2进制转换

9.3原码反码补码

10数组

10.1数组的基本概念

10.2定义数组

10.3初始化数组

定义的同时初始化

先定义后初始化

没有初始化会怎样?

10.4数组的使用

10.5数组的遍历

10.6数组长度计算方法

10.7数组的越界问题

10.8数组的注意事项

10.9数组名作为函数参数

数组名作函数参数的注意点

11排序算法

11.1计数排序

 11.2选择排序

11.3冒泡排序

11.4插入排序

11.5希尔排序

12二维数组

 12.1二维数组的定义

12.2二维数组的初始化

12.3二维数组的遍历

13字符串

13.1字符串的基本概念

 13.2字符串的初始化

13.3字符串输出

13.4字符串长度和函数

利用sizeof字符串长度

利用系统函数

字符串连接函数:strcat

字符串拷贝函数:strcpy

字符串比较函数:strcmp

13.4字符串数组基本概念

14指针

14.1指针基本概念

什么是地址?

 什么是指针?

什么是指针变量?

14.2定义指针变量的格式

14.3指针变量的初始化方法

14.4访问指针所指向的存储空间

14.5指针类型

14.6二级指针

 14.7数组指针的概念及定义

 14.8指针访问数组元素


01初识C语言

1.1语言

什么是C语言?

C语言是一门计算机语言。既有高级语言的特点,又有汇编语言的特点。

几乎所有的操作系统、计算机底层软件、编辑器都是C语言编写的。

计算机语言是什么?

人和计算机交流的语言。

C/C++/JAVA/Python都是高级语言。

1.2语言的发展

二进制语言 101000010101就只能这样写   低级语言 →汇编语言→后来ADD-10100001-助记符→B语言→C语言 C++语言

C语言一开始不成熟→成熟→流行→然后就有了国际标准

1.3计算机的思维方式

人的思维方式:

 计算机的思维方式:(枚举法)

 二分法

 1.4程序的执行

解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行。

编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机真正能懂的语言——机器语言——写的程序,然后,这个机器语言写的程序就能直接执行了

1.5计算机如何解决问题

人:请给我拿一杯水。(what to do)

计算机:请给我一杯水  1.转身走到厨房。2.找到一个杯子。3.找到一个水壶。4.在杯子中倒入一些水。5.拿着杯子走回桌面。(how to do)

程序是用特殊的编程语言写出来表达如何解决问题的。

不适用编程语言来和计算机交谈 ,而是描述要求它如何做事情的过程或方法。

1.6算法

·我们要让计算机做计算,就需要像这样找出计算的步骤,然后用编程语言写出来

·计算机做的所有的事情都叫做计算

·计算的步骤就是算法

1.7C语言编程软件

C需要被编译才能运行,所以需要:编译器 编辑器 或者 IDE(集成开发环境)

这里使用Dev C++

还有其他的选择

02第一个C语言程序

devc++

#include<stdio.h>

int main()
{
	printf("hello world!\n")
	
	return 0;
 } 

vs2022

写C代码:
(1).创建工程或者新建项目

 (2).创建文件

C代码     .c文件-源文件         .h文件-头文件(head)

添加源文件(比如test.c)

  (3).写代码

//包含一个叫stdio.h的文件
#include<stdio.h>
int main() //int是整型
//main前面的int表示main函数调用返回一个整型值
{
    //这里完成任务
    //在屏幕上输入hello world
    //函数
    printf("hello world\n");
    return 0;
}

ctrl + f5 运行代码 / ctrl+fn+F5

main()函数-主函数-程序的入口

一个程序中不能没有main函数,main函数有且仅有一个。

printf-库函数-C语言本身提供给我们使用的函数

#define_CRT_SECURE_NO_WARNINGS 1、

加载源文件的第一行

我们将它添加在如下文件中

  之后打开之后就会带有默认这句话

2.1程序框架

本课程中所有的程序都需要用到这一段,直到学函数之前,我们的代码都只是在这个框架中间。

主函数定义的格式:

  • int 代表函数执行之后会返回一个整数类型的值
  • main 代表这个函数的名字叫做main
  • () 代表这是一个函数
  • {} 代表这个程序段的范围
  • return 0; 代表函数执行完之后返回整数0

其它函数定义的格式:

  • int 代表函数执行之后会返回一个整数类型的值
  • call 代表这个函数的名字叫做call
  • () 代表这是一个函数
  • {} 代表这个程序段的范围
  • return 0; 代表函数执行完之后返回整数0
int call() {
    return 0;
}

输出(printf函数)

printf("Hello World!\n");

"里面的内容叫做“字符串”,printf会把其中的内容原封不动地输出,\n表示需要在输出的结果后面换一行。

printf函数称之为格式输出函数,方法名称的最后一个字母f表示format。其功能是按照用户指定的格式,把指定的数据输出到屏幕上
printf函数的调用格式为:
printf("格式控制字符串",输出项列表 );
例如:printf("a = %d, b = %d",a, b);
非格式字符串原样输出, 格式控制字符串会被输出项列表中的数据替换
注意: 格式控制字符串和输出项在数量和类型上必须一一对应

格式控制字符串

形式: %[标志][输出宽度][.精度][长度]类型

类型
格式: printf("a = %类型", a);
类型字符串用以表示输出数据的类型, 其格式符和意义如下所示
类型 含义

d有符号10进制整型
i有符号10进制整型
u无符号10进制整型
o无符号8进制整型
x无符号16进制整型
X无符号16进制整型
f单、双精度浮点数(默认保留6位小数)
e/E以指数形式输出单、双精度浮点数
g/G以最短输出宽度,输出单、双精度浮点数
c字符
s字符串
p地址

如何找到printf函数的实现代码

由于printf函数是系统实现的函数, 所以想要使用printf函数必须在使用之前告诉系统去哪里可以找到printf函数的实现代码
#include <stdio.h> 就是告诉系统可以去stdio这个文件中查找printf函数的声明和实现

程序中的错误

 编译的时候发现的错误所在的地方会以红色的底表示出来

具体的错误原因列在下方的窗口里(是英文的)

C的编译器给出的错误提示往往不那么好“猜”

不要用中文输入!!

中国学生还有一个极其常见的低级错误,就是用了中文输入法来输入程序。那些标点符号,在中文和英文可能看上去相似,但是对于计算机是完全不同的符号,如果你还开了全角标点的话,问题就更严重了。

main函数注意点及其他写法

  • C语言中,每条完整的语句后面都必须以分号结尾
  • C语言中除了知识和双引号引起来的地方以外都不能出现中文
  • 一个C语言程序只能有一个main函数
  • 一个C语言程序中不能没有main函数
  • main函数前面的int可以不写或者换成void
  • main函数中的return 0可以不写
  • 语法错误编译器会直接报错,逻辑错误只不过是运行结果不正确不报错

2.2C语言程序组成

手机有很多功能, “开机”,“关机”,“打电话”,“发短信”,"拍照"等等。手机中的每一个功能就相当于C语言程序中的一个程序段(函数)。
众多功能中总有一个会被先执行,不可能多个功能一起执行。
想使用手机必须先执行手机的开机功能。所以C语言程序也一样,由众多功能、众多程序段组成, 众多C语言程序段中总有一个会被先执行, 这个先执行的程序段我们称之为"主函数"。

  • 一个C语言程序由多个"函数"构成,每个函数有自己的功能
  • 一个程序有且只有一个主函数
  • 如果一个程序没有主函数,则这个程序不具备运行能力
  • 程序运行时系统会自动调用主函数,而其它函数需要开发者手动调用
  • 主函数有固定书写的格式和范写
  • 1.9函数定义格式

2.3做点计算

printf("%d\n",23+43);%d说明后面有一个整数要输出在这个位置上

%d的意思是让printf在这个地方填一个值进去,%d用后面的代替。然后会输出后面逗号隔开的值。

printf("23+43=%d\n",23+43);

 四则运算都可以做。

2.4注释

  • 注释是在所有计算机语言中都非常重要的一个概念,从字面上看,就是注解、解释的意思
  • 注释可以用来解释某一段程序或者某一行代码是什么意思,方便程序员之间的交流沟通
  • 注释可以是任何文字,也就是说可以写中文
  • 被注释的内容在开发工具中会有特殊的颜色

注释的分类

单行注释

// 被注释内容
使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面
作用范围: 从第二个斜线到这一行末尾
快捷键:Ctrl+/
多行注释
/* 被注释内容 */
使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面
作用范围: 从第一个/*到最近的一个*/

注释的注意点

单行注释可以嵌套单行注释、多行注释

// 6 // 
// /* 6 */
// 6


多行注释可以嵌套单行注释

/*
// 作者:6
// 描述:第一个C语言程序作用:这是一个主函数,C程序的入口点
 */

多行注释不能嵌套多行注释

/* 
哈哈哈
     /*嘻嘻嘻*/
 呵呵呵 
*/

注释的应用场景

  • 思路分析
  • 对变量进行说明
  • 对函数进行说明
  • 多实现逻辑排序

使用注释的好处

  • 注释是一个程序员必须要具备的良好习惯
  • 帮助开发人员整理实现思路
  • 解释说明程序, 提高程序的可读性
  • 初学者编写程序可以养成习惯:先写注释再写代码。将自己的思想通过注释先整理出来,在用代码去体现。因为代码仅仅是思想的一种体现形式而已

03变量

3.1变量定义

算找零(如何能在程序运行时输入数字23,然后计算输入结果?)

printf("23+43=%d\n",23+43);

printf("100-23=%d\n",100-23);

有地方放输入的数字     有办法输入数字        输入的数字能参与运算

如何输入

输入也在终端窗口

输入是以行为单位进行的,行的结束标志就是你按下了回车键。在你按下回车之前,你的程序不会读到任何东西

变量

int price = 0; 定义了一个变量  变量的名字是price  类型是int  初始值为0。

变量是一个保存数据的地方,当我们需要在程序里保存数据时,比如上面的例子中要记录用户输入的价格,就需要一个变量来保存它。用一个变量保存了数据,它十能参加到后面的计算中,比如计算找零。

 怎么定义变量

格式1: 变量类型 变量名称 ;
为什么要定义变量?
任何变量在使用之前,必须先进行定义, 只有定义了变量才会分配存储空间, 才有空间存储数据
为什么要限定类型?
用来约束变量所存放数据的类型。一旦给变量指明了类型,那么这个变量就只能存储这种类型的数据
内存空间极其有限,不同类型的变量占用不同大小的存储空间
为什么要指定变量名称?
存储数据的空间对于我们没有任何意义, 我们需要的是空间中存储的值
只有有了名称, 我们才能获取到空间中的值

int a;
float b;
char ch;

格式2:变量类型 变量名称,变量名称;
连续定义, 多个变量之间用逗号(,)号隔开

int a,b,c;

变量名的命名的规范
变量名属于标识符,所以必须严格遵守标识符的命名原则

如何修改变量值?

多次赋值即可。每次赋值都会覆盖原来的值。

变量之间的传递

可以将一个变量存储的值赋值给零一个变量。

如何查看变量的值?

使用printf输出一个或多个变量的值。

输出其他类型变量的值

3.2C语言的关键字

什么是关键字?

关键字,也叫作保留字。是指一些被C语言赋予了特殊含义的单词
关键字特征:
全部都是小写
在开发工具中会显示特殊颜色
关键字注意点:
因为关键字在C语言中有特殊的含义, 所以不能用作变量名、函数名等
C语言中一共有32个关键字

关键字分类

 3.3标识符

什么是标识符?

从字面上理解,就是用来标识某些东西的符号,标识的目的就是为了将这些东西区分开来。
其实标识符的作用就跟人类的名字差不多,为了区分每个人,就在每个人出生的时候起了个名字
C语言是由函数构成的,一个C程序中可能会有多个函数,为了区分这些函数,就给每一个函数都起了个名称, 这个名称就是标识符
综上所述: 程序员在程序中给函数、变量等起名字就是标识符。

标识符命名规则

  • 只能由字母(a~z、 A~Z)、数字、下划线组成
  • 不能包含除下划线以外的其它特殊字符串
  • 不能以数字开头
  • 不能是C语言中的关键字
  • 标识符严格区分大小写, test和Test是两个不同的标识符

 3.4赋值和初始化

赋值

 初始化

变量初始化

<类型名称><变量名称>=<初始值>;

int price = 0;  int amount = 100;   int price = 0,amount =100;

不初始化里面存储什么?

  • 随机数
  • 上次程序分配的存储空间,存储一些内内容"垃圾"
  • 系统正在用的一些数据

3.5变量类型

C是一种有类型的语言,所有的变量在使用之前必须定义或声明,所有的变量必须具有确定的数据类型。数据类型表示在变量中可以存放什么样的数据,变量中只能存放指定类型的数据,程序运行过程中笔不能改变变量的类型。

3.6程序输入(scanf函数)

 scanf函数用于接收键盘输入的内容, 是一个阻塞式函数,程序会停在scanf函数出现的地方, 直到接收到数据才会执行后面的代码
scanf函数的调用格式为:
scanf("格式控制字符串", 地址列表);
例如: scanf("%d", &num);

基本用法

地址列表项中只能传入变量地址, 变量地址可以通过&符号+变量名称的形式获取

#include <stdio.h>
int main(){
    int number;
    scanf("%d", &number); // 接收一个整数
    printf("number = %d\n", number); 
}

接收非字符和字符串类型时, 空格、Tab和回车会被忽略
 

#include <stdio.h>
int main(){
    float num;
    // 例如:输入 Tab 空格 回车 回车 Tab 空格 3.14 , 得到的结果还是3.14
    scanf("%f", &num);
    printf("num = %f\n", num);
}

 接收多条数据
格式控制字符串和地址列表项在数量和类型上必须一一对应
非字符和字符串情况下如果没有指定多条数据的分隔符, 可以使用空格或者回车作为分隔符(不推荐这种写法)
非字符和字符串情况下建议明确指定多条数据之间分隔符

#include <stdio.h>
int main(){
    int number;
    scanf("%d", &number);
    printf("number = %d\n", number);
    int value;
    scanf("%d", &value);
    printf("value = %d\n", value);
}

#include <stdio.h>
int main(){
    int number;
    int value;
    // 可以输入 数字 空格 数字, 或者 数字 回车 数字
    scanf("%d%d", &number, &value);
    printf("number = %d\n", number);
    printf("value = %d\n", value);
}
#include <stdio.h>
int main(){
    int number;
    int value;
    // 输入 数字,数字 即可
    scanf("%d,%d", &number, &value);
    printf("number = %d\n", number);
    printf("value = %d\n", value);
}

\n是scanf函数的结束符号, 所以格式化字符串中不能出现\n

#include <stdio.h>
int main(){
    int number;
    // 输入完毕之后按下回车无法结束输入
    scanf("%d\n", &number);
    printf("number = %d\n", number);
}

 3.7 常量

固定不变的数,是常数。直接写在程序里,我们称作直接量。

更好的方式,是定义一个常量:const int AMOUNT = 100;

 

 const

 

 让用户输入变量AMOUNT的值,而不是使用固定的值,这个变量在哪里定义合适?

 

常量的类型

整型常量

十进制整数。例如:666,-120, 0
八进制整数,八进制形式的常量都以0开头。例如:0123,也就是十进制的83;-011,也就是十进 制的-9
十六进制整数,十六进制的常量都是以0x开头。例如:0x123,也就是十进制的291
二进制整数,逢二进一 0b开头。例如: 0b0010,也就是十进制的2
实型常量

小数形式
单精度小数:以字母f或字母F结尾。例如:0.0f、1.01f
双精度小数:十进制小数形式。例如:3.14、 6.66
默认就是双精度
可以没有整数位只有小数位。例如: .3、 .6f
指数形式
以幂的形式表示, 以字母e或字母E后跟一个10为底的幂数
上过初中的都应该知道科学计数法吧,指数形式的常量就是科学计数法的另一种表 示,比如123000,用科学计数法表示为1.23×10的5次方
用C语言表示就是1.23e5或1.23E5
字母e或字母E后面的指数必须为整数
字母e或字母E前后必须要有数字
字母e或字母E前后不能有空格
字符常量

字符型常量都是用’’(单引号)括起来的。例如:‘a’、‘b’、‘c’
字符常量的单引号中只能有一个字符
特殊情况: 如果是转义字符,单引号中可以有两个字符。例如:’\n’、’\t’
字符串常量

字符型常量都是用""(双引号)括起来的。例如:“a”、“abc”、“lnj”
系统会自动在字符串常量的末尾加一个字符’\0’作为字符串结束标志
自定义常量

3.8浮点数

带小数点的数值。浮点这个词的本意就是指小数点是浮动的,是计算机内部表达非整数(包含分数和无理数)的一种方式。另一种方式叫做定点数,不过在c语言中你不会遇到定点数。人们借用浮点数这个词来表达所有的带小数点的数。

 当浮点数和整数放到一起运算时,C会将整数转化成浮点数,然后进行浮点数的运算。

double(%lf)

inch是定义为int类型的变量,如果把int换成double,我们就把它改为double类型的浮点数变量了。

double的意思是“双”,它本来是"双精度浮点数”的第一个单词,人们用来表示浮点数类型。除了double,还有float(意思就是浮点!)表示单精度浮点数。

数据类型

3.9局部变量和全局变量

全局变量:定义在代码块({})之外的变量

局部变量:定义在代码块({})内部

当局部变量和全局变量的名字建议不要相同-容易误会产生bug

当局部变量和全局变量相同时-局部变量会优先

3.10变量的作用域和生命周期

局部变量的作用域是变量所在的局部范围

全局变量的作用域是整个工程

局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。

全局变量的生命周期是:整个程序的生命周期。

3.11putchar和getchar

putchar: 向屏幕输出一个字符

#include <stdio.h>
int main(){
    char ch = 'a';
    putchar(ch); // 输出a

getchar: 从键盘获得一个字符

#include <stdio.h>
int main(){
    char ch;
    ch = getchar();// 获取一个字符
    printf("ch = %c\n", ch);
} 

 04表达式

一个表达式是一系列运算符和算子的组合,用来计算一个值。

4.1运算符

 计算时间差

运算符分类

按照功能划分:
算术运算符
赋值运算符
关系运算符
逻辑运算符
位运算符
按照参与运算的操作数个数划分:
单目运算
只有一个操作数 如 : i++;
双目运算
有两个操作数 如 : a + b;
三目运算
C语言中唯一的一个,也称为问号表达式 如: a>b ? 1 : 0;

运算符的优先级和结合性

早在小学的数学课本中,我们就学习过"从左往右,先乘除后加减,有括号的先算括号里面的", 这句话就蕴含了优先级和结合性的问题
C语言中,运算符的运算优先级共分为15 级。1 级最高,15 级最低
在C语言表达式中,不同优先级的运算符, 运算次序按照由高到低执行
在C语言表达式中,相同优先级的运算符, 运算次序按照结合性规定的方向执行

 

 算数运算符

优先级名称符号说明
3乘法运算符*双目运算符,具有左结合性
3除法运算符/双目运算符,具有左结合性
3求余运算符 (模运算符)%双目运算符,具有左结合性
4加法运算符+双目运算符,具有左结合性
4减法运算符-双目运算符,具有左结合性

注意事项

  • 如果参与运算的两个操作数皆为整数, 那么结果也为整数
  • 如果参与运算的两个操作数其中一个是浮点数, 那么结果一定是浮点数
  • 求余运算符, 本质上就是数学的商和余"中的余数
  • 求余运算符, 参与运算的两个操作数必须都是整数, 不能包含浮点数
  • 求余运算符, 被除数小于除数, 那么结果就是被除数
  • 求余运算符, 运算结果的正负性取决于被除数,跟除数无关, 被除数是正数结果就是正数,被除数是负数结果就是负数
  • 求余运算符, 被除数为0, 结果为0
  • 求余运算符, 除数为0, 没有意义(不要这样写)

赋值运算符

优先级名称符号说明
14赋值运算符=双目运算符,具有右结合性
14

除后赋值运算符

/=双目运算符,具有右结合性
14乘后赋值运算符 (模运算符)*=双目运算符,具有右结合性
14取模后赋值运算符%=双目运算符,具有右结合性
14加后赋值运算符+=双目运算符,具有右结合性
14减后赋值运算符-=双目运算符,具有右结合性

复合赋值

5个算数运算符,+-*/%,可以和赋值运算符"="结合起来,形成复合赋值运算符:"+=","-=","*=","/="和"%="

total += 5; → total = total + 5;(两个运算符中间不要有空格)

递增递减运算符

在程序设计中,经常遇到“i=i+1”和“i=i-1”这两种极为常用的操作。

C语言为这种操作提供了两个更为简洁的运算符,即++和–

优先级名称符号说明
2自增运算符(在后)i++单目运算符,具有左结合性
2自增运算符(在前)++i单目运算符,具有右结合性
2自减运算符(在后)i--单目运算符,具有左结合性
2自减运算符(在前)--i单目运算符,具有右结合性

 前缀后缀!!!

++和--可以放在变量的前面,叫做前缀形式,也可以放在变量的后面,叫做后缀形式。

a++的值是a加1以前的值,而++a的值是加了1以后的值,无论哪个,a自己的值都加1了。

如果只有单个变量, 无论++写在前面还是后面都会对变量做+1操作。

如果出现在一个表达式中, 那么++写在前面和后面就会有所区别

  •     前缀表达式:++x, --x;其中x表示变量名,先完成变量的自增自减1运算,再用x的值作为表达式的值;即“先变后用”,也就是变量的值先变,再用变量的值参与运算
  •     后缀表达式:x++, x–;先用x的当前值作为表达式的值,再进行自增自减1运算。即“先用后变”,也就是先用变量的值参与运算,变量的值再进行自增自减变化

注意点:

  • 自增、自减运算只能用于单个变量,只要是标准类型的变量,不管是整型、实型,还是字符型变量等,但不能用于表达式或常量
    • 错误用法: ++(a+b); 5++;
  • 企业开发中尽量让++ – 单独出现, 尽量不要和其它运算符混合在一起

sizeof运算符

sizeof可以用来计算一个变量或常量、数据类型所占的内存字节数。

标准格式: sizeof(常量 or 变量);

sizeof的几种形式

    sizeof( 变量\常量 );
        sizeof(10);
        char c = 'a'; sizeof(c);
    sizeof 变量\常量;
        sizeof 10;
        char c = 'a'; sizeof c;
    sizeof( 数据类型);
        sizeof(float);
        如果是数据类型不能省略括号

注意!

  • sizeof()和+=、*=一样是一个复合运算符, 由sizeof和()两个部分组成, 但是代表的是一个整体
  • 所以sizeof不是一个函数, 是一个运算符, 该运算符的优先级是2

逗号运算符

在C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把多个表达式连接起来组成一个表达式,称为逗号表达式。
逗号运算符会从左至右依次取出每个表达式的值, 最后整个逗号表达式的值等于最后一个表达式的值
格式: 表达式1,表达式2,… …,表达式n;

 例如: int result = a+1,b=3*4;

#include <stdio.h>
int main(){
    int a = 10, b = 20, c;
    // ()优先级高于逗号运算符和赋值运算符, 所以先计算()中的内容
    // c = (11, 21);
    // ()中是一个逗号表达式, 结果是最后一个表达式的值, 所以计算结果为21
    // 将逗号表达式的结果赋值给c, 所以c的结果是21
    c = (a + 1, b + 1);
    printf("c = %i\n", c); // 21
}

关系运算符

为什么要学习关系运算符?

  • 默认情况下,我们在程序中写的每一句正确代码都会被执行。但很多时候,我们想在某个条件成立的情况下才执行某一段代码。
  • 这种情况的话可以使用条件语句来完成,但是学习条件语句之前,我们先来看一些更基础的知识:如何判断一个条件是否成立
  • 关系运算符的运算结果只有2种:如果条件成立,结果就为1,也就是“真”;如果条件不成立,结果就为0,也就是“假”
优先级名称符号说明
6大于运算符>双目运算符,具有左结合性
6小于运算符<双目运算符,具有左结合性
6大于等于运算符>=双目运算符,具有左结合性
6小于等于运算符<=双目运算符,具有左结合性
7等于运算符==双目运算符,具有左结合性
7不等于运算符!=双目运算符,具有左结合性

逻辑运算符

优先级名称符号说明
2逻辑非运算符!单目运算符,具有右结合性
11逻辑与运算符&&双目运算符,具有左结合性
12逻辑或运算符\|\|双目运算符,具有左结合性

 逻辑非

    格式: ! 条件A;
    运算结果: 真变假,假变真
    运算过程:
        先判断条件A是否成立,如果添加A成立, 那么结果就为0,即“假”;
        如果条件A不成立,结果就为1,即“真”
    使用注意:
        可以多次连续使用逻辑非运算符
        !!!0;相当于(!(!(!0)));最终结果为1

逻辑与

    格式: 条件A && 条件B;
    运算结果:一假则假
    运算过程:
        总是先判断"条件A"是否成立
        如果"条件A"成立,接着再判断"条件B"是否成立, 如果"条件B"也成立,结果就为1,即“真”
        如果"条件A"成立,"条件B"不成立,结果就为0,即“假”
        如果"条件A"不成立,不会再去判断"条件B"是否成立, 因为逻辑与只要一个不为真结果都不为真
    使用注意:
        "条件A"为假, "条件B"不会被执行

逻辑或

    格式: 条件A || 条件B;
    运算结果:一真则真
    运算过程:
        总是先判断"条件A"是否成立
        如果"条件A"不成立,接着再判断"条件B"是否成立, 如果"条件B"成立,结果就为1,即“真”
        如果"条件A"不成立,"条件B"也不成立成立, 结果就为0,即“假”
        如果"条件A"成立, 不会再去判断"条件B"是否成立, 因为逻辑或只要一个为真结果都为真
    使用注意:
        "条件A"为真, "条件B"不会被执行

三目运算符

  • 三目运算符,它需要3个数据或表达式构成条件表达式

  • 格式: 表达式1?表达式2(结果A):表达式3(结果B)

    • 示例: 考试及格 ? 及格 : 不及格;

  • 求值规则:

    • 如果"表达式1"为真,三目运算符的运算结果为"表达式2"的值(结果A),否则为"表达式3"的值(结果B)。

注意:

  • 条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符
  • 条件运算符?和:是一个整体,不能分开使用

4.2交换

05判断和分支(if-else)

5.1流程控制基本概念

默认情况下程序运行后,系统会按书写顺序从上至下依次执行程序中的每一行代码。但是这并不能满足我们所有的开发需求, 为了方便我们控制程序的运行流程,C语言提供3种流程控制结构,不同的流程控制结构可以实现不同的运行流程。

这3种流程结构分别是顺序结构、选择结构、循环结构

顺序结构

    按书写顺序从上至下依次执行

选择结构

对给定的条件进行判断,再根据判断结果来决定执行代码

 

循环结构

在给定条件成立的情况下,反复执行某一段代码

 

5.2选择结构-if语句

if第一种形式

表示如果表达式为真,执行语句块1,否则不执行

if(表达式) {
  语句块1;
}
后续语句;

 if第二种形式

如果表达式为真,则执行语句块1,否则执行语句块2

else不能脱离if单独使用

if(表达式){
  语句块1;
}else{
  语句块2;
}
后续语句;

if第三种形式

    如果"表达式1"为真,则执行"语句块1",否则判断"表达式2",如果为真执行"语句块2",否则再判断"表达式3",如果真执行"语句块3", 当表达式1、2、3都不满足,会执行最后一个else语句
    众多大括号中,只有一个大括号中的内容会被执行
    只有前面所有添加都不满足, 才会执行else大括号中的内容

if(表达式1) {
  语句块1;
}else if(表达式2){
  语句块2;
}else if(表达式3){
  语句块3;
}else{
  语句块4;
}
后续语句;

注意 !

一个基本的if语句由一个关键字if开头,跟上在括号里的一个表示条件的逻辑表达式,然后是一对大括号“{}”之间的若干条语句。如果表示条件的逻辑表达式的结果不是零,那么就执行后面跟着的这对大括号中的语句,否则就跳过这些语句不执行,而继续下面的其他语句。

if嵌套

if中可以继续嵌套if, else中也可以继续嵌套if

if(表达式1){
    语句块1;
   if(表达式2){
      语句块2;
  }
}else{
   if(表达式3){
      语句块3;
  }else{
      语句块4;
  }
}

 if注意点

  • 当if else后面只有一条语句时, if else后面的大括号可以省略
    // 极其不推荐写法
    int age = 17;
    if (age >= 18)
        printf("开网卡\n");
    else
        printf("喊家长来开\n");
  •  当if else后面的大括号被省略时, else会自动和距离最近的一个if匹配(在if或者else后面总是用{},即使只有一条语句的时候)
#include <stdio.h>
int main(){
    if(0)
    if(1)
    printf("A\n");
    else // 和if(1)匹配
    printf("B\n");
    else // 和if(0)匹配, 因为if(1)已经被匹配过了
    if (1)
    printf("C\n"); // 输出C
    else // 和if(1)匹配
    printf("D\n");
}
  • 如果if else省略了大括号, 那么后面不能定义变量
#include <stdio.h>
int main(){
    if(1)
        int number = 10; // 系统会报错
    printf("number = %i\n", number);
}
  • C语言中分号(;)也是一条语句, 称之为空语句
// 因为if(10 > 2)后面有一个分号, 所以系统会认为if省略了大括号
// if省略大括号时只能管控紧随其后的那条语句, 所以只能管控分号
if(10 > 2);
{
printf("10 > 2");
}
// 输出结果: 10 > 2
  • 但凡遇到比较一个变量等于或者不等于某一个常量的时候,把常量写在前面
#include <stdio.h>
int main(){
    int a = 8;
//    if(a = 10){// 错误写法, 但不会报错
    if (10 == a){
      printf("a的值是10\n");
    }else{
     printf("a的值不是10\n");
    }
}

5.3选择结构-switch

switch语句

  • 由于 if else if 还是不够简洁,所以switch 就应运而生了,他跟 if else if 互为补充关系。switch 提供了点的多路选择

格式:

switch(表达式){
    case 常量表达式1:
        语句1;
        break;
    case 常量表达式2:
        语句2; 
        break;
    case 常量表达式n:
        语句n;
        break;
    default:
        语句n+1;
        break;
}

语义

  • 计算"表达式"的值, 逐个与其后的"常量表达式"值相比较,当"表达式"的值与某个"常量表达式"的值相等时, 即执行其后的语句, 然后跳出switch语句
  • 如果"表达式"的值与所有case后的"常量表达式"均不相同时,则执行default后的语句

switch注意点

  • switch条件表达式的类型必须是整型, 或者可以被提升为整型的值(char、short)
  • +case的值只能是常量, 并且还必须是整型, 或者可以被提升为整型的值(char、short)
  • case后面常量表达式的值不能相同
  • case后面要想定义变量,必须给case加上大括号
  • switch中只要任意一个case匹配, 其它所有的case和default都会失效. 所以如果case和default后面没有break就会出现穿透问题
  • switch中default可以省略
  • switch中default的位置不一定要写到最后, 无论放到哪都会等到所有case都不匹配才会执行(穿透问题除外)

break

switch语句可以看作是一种基于计算的跳转,计算控制表达式的值后,程序会跳转到相匹配的case(分支标号)处。分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后,如果后面没有break,就会顺序执行到下面的case里去,直到遇到一个break,或者switch结束为止。

06循环语句

  • C语言中提供了三大循环结构, 分别是while、dowhile和for
  • 循环结构是程序中一种很重要的结构。
    • 其特点是,在给定条件成立时,反复执行某程序段, 直到条件不成立为止。
    • 给定的条件称为"循环条件",反复执行的程序段称为"循环体"

6.1while循环

"当"条件满足时,不断的重复循环体内的语句。

循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行。

条件成立是循环继续的条件。

while (  循环控制条件 ) {
    循环体中的语句;
    能够让循环结束的语句;
    ....
}

while循环注意点

当while后面只有一条语句时,while后面的大括号可以省略

如果while省略了大括号, 那么后面不能定义变量

语言中分号(;)也是一条语句, 称之为空语句

最简单的死循环

// 死循环一般在操作系统级别的应用程序会比较多, 日常开发中很少用
while (1);

 验证

6.2 do-while循环

在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,如果满足则继续下一轮循环,不满足则结束循环.

 

 6.3循环应用1

猜数游戏

 大概步骤

 循环的条件是:a和number不相等

 

随机数:每次召唤rand()就得到一个随机的整数

%100:x%n的结果是[0,n-1]的一个整数

 算平均数

 大概步骤

 

6.4第三种循环-for循环

for循环像一个计数循环:设定一个计数器,初始化它,然后在计数器到达某值之前,重复执行循环体,而每执行一轮循环,计数器值以一定步进进行调整,比如加I或者减l。

 

for(初始化表达式;循环条件表达式;循环后的操作表达式) {
    循环体中的语句;
}

for循环执行流程

  • 首先执行"初始化表达式",而且在整个循环过程中,只会执行一次初始化表达式
  • 接着判断"循环条件表达式"是否为真,为真执行循环体中的语句
  • 循环体执行完毕后,接下来会执行"循环后的操作表达式",然后再次判断条件是否为真,为真继续执行循环体,为假跳出循环
  • 重复上述过程,直到条件不成立就结束for循环

for循环注意点

  • 和while一模一样
  • 最简单的死循环for(;;);

for和while应用场景

  • while能做的for都能做, 所以企业开发中能用for就用for, 因为for更为灵活
  • 而且对比while来说for更节约内存空间

6.5循环的计算和选择

 循环次数

 for==while

 三种循环相比

 如果有固定次数,用for

如果必须执行一次,用do-while

其他情况用while

while和do while应用场景

  • 绝大多数情况下while和dowhile可以互换, 所以能用while就用while
  • 无论如何都需要先执行一次循环体的情况, 才使用dowhile
  • do while 曾一度提议废除,但是他在输入性检查方面还是有点用的

 6.6循环控制(四大跳转)

C语言中提供了四大跳转语句, 分别是return、break、continue、goto

break

立即跳出switch语句或循环

应用场景

switch  循环结构

 break注意点

  • break离开应用范围,存在是没有意义的
  • 在多层循环中,一个break语句只向外跳一层
  • break下面不可以有语句,因为执行不到

continue

  • 结束本轮循环,进入下一轮循环

应用场景

循环结构

 continue注意点

continue离开应用范围,存在是没有意义的

goto

  • 这是一个不太值得探讨的话题,goto 会破坏结构化程序设计流程,它将使程序层次不清,且不易读,所以慎用
  • goto 语句,仅能在本函数内实现跳转,不能实现跨函数跳转(短跳转)。但是他在跳出多重循环的时候效率还是蛮高的

 return

  • 结束当前函数,将结果返回给调用者
  • 不着急, 放一放,学到函数我们再回头来看它

break和continue只能对它所在的那层循环做

接力break

 如何用break和continue来控制循环

break 跳出循环

continue 跳过循环这一轮剩下的语句进入下一轮

 

6.7循环的嵌套

循环结构的循环体中存在其他的循环结构,我们称之为循环嵌套

  • 注意: 一般循环嵌套不超过三层
  • 外循环执行的次数*内循环执行的次数就是内循环总共执行的次数

格式

while(条件表达式) {
    while循环结构 or dowhile循环结构 or for循环结构
}
for(初始化表达式;循环条件表达式;循环后的操作表达式) {
    while循环结构 or dowhile循环结构 or for循环结构
}
do {
     while循环结构 or dowhile循环结构 or for循环结构
} while (循环控制条件 );

循环优化

在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数

07数据类型

7.1数据

什么是数据?

生活中无时无刻都在跟数据打交道
例如:人的体重、身高、收入、性别等数据等
在我们使用计算机的过程中,也会接触到各种各样的数据
例如: 文档数据、图片数据、视频数据等

数据分类

静态的数据
静态数据是指一些永久性的数据,一般存储在硬盘中。硬盘的存储空间一般都比较大,现在普通计算机的硬盘都有500G左右,因此硬盘中可以存放一些比较大的文件
存储的时长:计算机关闭之后再开启,这些数据依旧还在,只要你不主动删掉或者硬盘没坏,这些数据永远都在
哪些是静态数据:静态数据一般是以文件的形式存储在硬盘上,比如文档、照片、视频等。
动态的数据

动态数据指在程序运行过程中,动态产生的临时数据,一般存储在内存中。内存的存储空间一般都比较小,现在普通计算机的内存只有8G左右,因此要谨慎使用内存,不要占用太多的内存空间
存储的时长:计算机关闭之后,这些临时数据就会被清除
哪些是动态数据:当运行某个程序(软件)时,整个程序就会被加载到内存中,在程序运行过程中,会产生各种各样的临时数据,这些临时数据都是存储在内存中的。当程序停止运行或者计算机被强制关闭时,这个程序产生的所有临时数据都会被清除。
既然硬盘的存储空间这么大,为何不把所有的应用程序加载到硬盘中去执行呢?
主要原因就是内存的访问速度比硬盘快N倍

7.2C语言的数据类型

作为程序员, 我们最关心的是内存中的动态数据,因为我们写的程序就是在内存中运行的
程序在运行过程中会产生各种各样的临时数据,为了方便数据的运算和操作, C语言对这些数据进行了分类, 提供了丰富的数据类型
C语言中有4大类数据类型:基本类型、构造类型、指针类型、空类型

char字符数据类型
short短整型
int整型
long长整型
long long更长的整型
float单精度浮点数
double双精度浮点数

整数:char short int long longlong

浮点数:float double longdouble

逻辑:bool

指针

自定义类型

选择整数类型

为什么整数要有那么多种?

为了准确表达内存,做底层程序的需要

没有特殊需要,就选择int

现在的CPU的字长普遍是32位或64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢

*现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小(虽然sizeof告诉你更小)

unsigned与否只是输出的不同,内部计算是一样的

类型有何不同

类型名称输出输出时格式化
char%c
int%d
long%id
double%if
float

%f

以地址的形式打印%p

所表达的数的范围:char<short<int<float<double

内存中所占据的大小:1个字节到16个字节

内存中的表达形式:二进制数(补码)、编码

char 字符类型

int main()
{
    char ch = 'A';//像内存申请了一块空间存放字符A
    printf("%c\n",ch);//%c--打印字符格式的数据
    return 0;
}

int 整型

    int main()
    {
        int age = 20;
        printf("%d\n",age);//%d--打印整型十进制数据
    }

short int 短整型      long int 长整型     longlong int 更长的整型

float

    int main()
    {
        float f = 5.0;
        printf("%f\n",f);
        return 0;    
    }

double

    int main()
    {
        double d = 3.14;
        printf("%lf\n",f);
        return 0;    
    }

超过范围的浮点数

 7.2字节

计算机中的单位 

最小的单位-bit-比特位--只能存1或者0--一个比特位用来存放一个二进制位

byte-字节-一个字节是8个比特位

kb-1024个字节

mb -104个kb

gb-1024个mb

tb-1024个gb

pb-1024个pb

数据类型字节大小放的二进制可能性
char12^8
short22^16

int

42^32
long4/82^32/2^64
long long82^64
float4

2^32

double82^64

C语言标准规定 sizeof(long)>=sizeof(int)

7.3类型转换

自动类型转换

当运算符的两边出现不一致的类型时,会自动转换成较大的类型

大的意思时能表达的数的范围更大

 对于printf,任何小于int的类型会被转换成int; float会被转换成double

但是scanf不会,要输入short,需要%hd

强制类型转换

要把一个量强制转换成另一个类型(通常是较小的类型),需要:·(类型)值

 

注意这时候的安全性,小的变量不总能表达大的量. (short)32768

 

 强制类型转换的优先级高于四则运算

7.4逃逸字符

字符意义字符意义
\b回退一格\"双引号
\t到下一个表位格\'单引号
\n换行\\反斜杠本身
\r回车

7.5bool

 

7.6signed和unsigned
首先要明确的:signed int等价于signed,unsigned int等价于unsigned

  • signed:表示有符号,也就是说最高位要当做符号位。但是int的最高位本来就是符号位,因此signed和int是一样的,signed等价于signed int,也等价于int。signed的取值范围是-2^31 ~ 2^31 - 1
  • unsigned:表示无符号,也就是说最高位并不当做符号位,所以不包括负数。
  • 因此unsigned的取值范围是:0000 0000 0000 0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111 1111 1111 1111,也就是0 ~ 2^32 - 1

08函数

8.1函数的基本概念

  • C源程序是由函数组成的

    例如: 我们前面学习的课程当中,通过main函数+scanf函数+printf函数+逻辑代码就可以组成一个C语言程序

  • C语言不仅提供了极为丰富的库函数, 还允许用户建立自己定义的函数。用户可把自己的算法编写成一个个相对独立的函数,然后再需要的时候调用它。
  • 可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言。

8.2函数的分类

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

  • 库函数: 由C语言系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf、scanf、getchar、putchar等函数均属此类
  • 用户定义函数:由用户按需编写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用

从函数执行结果的角度来看, 函数可分为有返回值函数和无返回值函数两种

  • 有返回值函数: 此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。(必须指定返回值类型和使用return关键字返回对应数据)
  • 无返回值函数: 此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。(返回值类型为void, 不用使用return关键字返回对应数据)

从主调函数和被调函数之间数据传送的角度看,又可分为无参函数和有参函数两种

  • 无参函数: 在函数定义及函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。
  • 有参函数: 在函数定义及函数说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)

8.3函数的定义

定义函数的目的:将一个常用的功能封装起来,方便以后调用

自定义函数的书写格式:

返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) {
    函数体;
    返回值;
}

函数名称不能相同

定义函数的步骤:

  • 函数名:函数叫什么名字
  • 函数体:函数是干啥的,里面包含了什么代码
  • 返回值类型: 函数执行完毕返回什么和调用者

无参无返回值函数定义

  • 没有返回值时return可以省略
  • 格式
void 函数名() {
    函数体;
}
// 1.没有返回值/没有形参
// 如果一个函数不需要返回任何数据给调用者, 那么返回值类型就是void
void printRose() {
    printf(" {@}\n");
    printf("  |\n");
    printf(" \\|/\n"); // 注意: \是一个特殊的符号(转意字符), 想输出\必须写两个斜线
    printf("  |\n");
  // 如果函数不需要返回数据给调用者, 那么函数中的return可以不写
}

无参有返回值函数定义

格式:

返回值类型 函数名() {
    函数体;
    return 值;
}
int getMax() {
    printf("请输入两个整数, 以逗号隔开, 以回车结束\n");
    int number1, number2;
    scanf("%i,%i", &number1, &number2);
    int max = number1 > number2 ? number1 : number2;
    return max;
}

有参无返回值函数定义

  • 形式参数表列表的格式: 类型 变量名,类型 变量2,......
  • 格式:
void 函数名(参数类型 形式参数1,参数类型 形式参数2,…) {
    函数体;
}
void printMax(int value1, int value2) {
    int max = value1 > value2 ? value1 : value2;
    printf("max = %i\n", max);
}

有参有返回值函数定义

格式:

返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) {
    函数体;
    return 0;
}
 int printMax(int value1, int value2) {
    int max = value1 > value2 ? value1 : value2;
    return max;
}

8.4函数的参数和返回值

形式参数

定义函数时,函数名后面小括号()中定义的变量称为形式参数,简称形参。

  • 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。
  • 因此,形参只有在函数内部有效,函数调用结束返回主调函数后则不能再使用该形参变量

实际参数

调用函数时, 传入的值称为实际参数,简称实参

  • 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参
  • 因此应预先用赋值,输入等办法使实参获得确定值

形参、实参注意点

  • 调用函数时传递的实参个数必须和函数的形参个数必须保持一致
  • 形参实参类型不一致, 会自动转换为形参类型
  • 当使用基本数据类型(char、int、float等)作为实参时,实参和形参之间只是值传递,修改形参的值并不影响到实参函数可以没有形参

返回值类型注意点

  • 如果没有写返回值类型,默认是int
一个函数内部可以多次使用return语句,但是return语句后面的代码就不再被执行
  • 函数返回值的类型和return实际返回的值类型应保持一致。如果两者不一致,则以返回值类型为准,自动进行类型转换
int height() {
    return 3.14; 
}
int main() {
  double temp = height();
  printf("%lf", temp);// 输出结果: 3.000000
}
  • 一个函数内部可以多次使用return语句,但是return语句后面的代码就不再被执行

函数的声明

在C语言中,函数的定义顺序是有讲究的:

默认情况下,只有后面定义的函数才可以调用前面定义过的函数

如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明, 否则

  • 系统搞不清楚有没有这个函数
  • 系统搞不清楚这个函数接收几个参数
  • 系统搞不清楚这个函数的返回值类型是什么

所以函数声明,就是在函数调用之前告诉系统, 该函数叫什么名称, 该函数接收几个参数, 该函数的返回值类型是什么.

函数的声明格式:

  • 将自定义函数时{}之前的内容拷贝到调用之间即可
  • 例如: int max( int a, int b );
  • 或者: int max( int, int );
// 函数声明
void getMax(int v1, int v2);
int main(int argc, const char * argv[]) {
    getMax(10, 20); // 调用函数
    return 0;
}
// 函数实现
void getMax(int v1, int v2) {
    int max = v1 > v2 ? v1 : v2;
    printf("max = %i\n", max);
}

函数的声明与实现的关系

  • 声明仅仅代表着告诉系统一定有这个函数, 和这个函数的参数、返回值是什么
  • 实现代表着告诉系统, 这个函数具体的业务逻辑是怎么运作的

函数声明注意点:

  • 函数的实现不能重复, 而函数的声明可以重复
  • 函数声明可以写在函数外面,也可以写在函数里面, 只要在调用之前被声明即可
  • 当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作声明
// 函数实现
void getMax(int v1, int v2) {
    int max = v1 > v2 ? v1 : v2;
    printf("max = %i\n", max);
}
int main(int argc, const char * argv[]) {
    getMax(10, 20); // 调用函数
    return 0;
}
  • 如果被调函数的返回值是整型时,可以不对被调函数作说明,而直接调用
int main(int argc, const char * argv[]) {
    int res = getMin(5, 3); // 不会报错
    printf("result = %d\n", res );
    return 0;
}
int getMin(int num1, int num2) {// 返回int, 不用声明
    return num1 < num2 ? num1 : num2;
}

8.5递归函数

一个函数在它的函数体内调用它自身称为递归调用

void function(int x){
    function(x);
}

递归函数构成条件

  • 自己搞自己
  • 存在一个条件能够让递归结束
  • 问题的规模能够缩小

递归和循环的区别

  • 能用循环实现的功能,用递归都可以实现
  • 递归常用于"回溯", “树的遍历”,"图的搜索"等问题
  • 但代码理解难度大,内存消耗大(易导致栈溢出), 所以考虑到代码理解难度和内存消耗问题, 在企业开发中一般能用循环都不会使用递归

09进制

9.1进制的基本概念

什么是进制?

进制是一种计数的方式,数值的表示形式

常见的进制

十进制、二进制、八进制、十六进制

进制书写的格式和规律

  • 十进制 0、1、2、3、4、5、6、7、8、9 逢十进一
  • 二进制 0、1 逢二进一

书写形式:需要以0b或者0B开头,例如: 0b101

  • 八进制 0、1、2、3、4、5、6、7 逢八进一

书写形式:在前面加个0,例如: 061

  • 十六进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 逢十六进一

书写形式:在前面加个0x或者0X,例如: 0x45

9.2进制转换

10 进制转 2 进制

  • 除2取余, 余数倒序; 得到的序列就是二进制表示形式
  • 例如: 将十进制(97) 10转换为二进制数

 2 进制转 10 进制

  • 每一位二进制进制位的值 * 2的当前索引次幂; 再将所有位求出的值相加
  • 例如: 将二进制01100100转换为十进制
01100100
索引从右至左, 从零开始
第0位: 0 * 2^0 = 0;
第1位: 0 * 2^1 = 0;
第2位: 1 * 2^2 = 4;
第3位: 0 * 2^3 = 0;
第4位: 0 * 2^4 = 0;
第5位: 1 * 2^5 = 32;
第6位: 1 * 2^6 = 64;
第7位: 0 * 2^7 = 0;
最终结果为: 0 + 0 + 4 + 0 + 0 + 32 + 64 + 0 = 100

2 进制转 8 进制

  • 三个二进制位代表一个八进制位, 因为3个二进制位的最大值是7,而八进制是逢8进1
  • 例如: 将二进制01100100转换为八进制数
从右至左每3位划分为8进制的1位, 不够前面补0
001 100 100
第0位: 100 等于十进制 4
第1位: 100 等于十进制 4
第2位: 001 等于十进制 1
最终结果: 144就是转换为8进制的值

2 进制转 16 进制

  • 四个二进制位代表一个十六进制位,因为4个二进制位的最大值是15,而十六进制是逢16进1
  • 例如: 将二进制01100100转换为十六进制数
从右至左每4位划分为16进制的1位, 不够前面补0
0110 0100
第0位: 0100 等于十进制 4
第1位: 0110 等于十进制 6
最终结果: 64就是转换为16进制的值

其它进制转换为十进制

系数 * 基数 ^ 索引 之和

    十进制           -->          十进制
   12345   =  10000 + 2000 + 300 + 40 + 5
           =  (1 * 10 ^ 4)  + (2 * 10 ^ 3) + (3 * 10 ^ 2) + (4 * 10 ^ 1) + (5 * 10 ^ 0)
           =  (1 * 10000) + (2 + 1000) + (3 * 100) + (4 * 10) + (5 * 1)
           =  10000 + 2000 + 300 + 40 + 5
           =  12345
   
   规律:
   其它进制转换为十进制的结果 = 系数 * 基数 ^ 索引 之和
   
   系数: 每一位的值就是一个系数 
   基数: 从x进制转换到十进制, 那么x就是基数
   索引: 从最低位以0开始, 递增的数
   二进制        -->      十进制
   543210
   101101 = (1 * 2 ^ 5) + (0 * 2 ^ 4) + (1 * 2 ^ 3) + (1 * 2 ^ 2) + (0 * 2 ^ 1) + (1 * 2 ^ 0)
          = 32 + 0 + 8 + 4 + 0 + 1
          = 45
   
   八进制        -->     十进制
   016  =   (0 * 8 ^ 2) + (1 * 8 ^ 1) + (6 * 8 ^ 0)
        =    0  + 8 + 6
        =    14
   
   十六进制      -->      十进制
   0x11f =  (1 * 16 ^ 2) + (1 * 16 ^ 1) + (15 * 16 ^ 0)
         =   256  + 16 + 15
         =   287

十进制快速转换为其它进制

十进制除以基数取余, 倒叙读取

   十进制        -->     二进制
   100          -->    1100100
   100 / 2   = 50     0
   50  / 2   = 25     0
   25  / 2   = 12     1
   12  / 2   = 6      0
   6   / 2   = 3      0
   3   / 2   = 1      1
   1   / 2   = 0      1
   
   
   十进制        -->     八进制
   100          -->     144
   100 / 8    = 12    4
   12  / 8    = 1     4
   1   / 8    = 0     1
   
   十进制        -->     十六进制
   100          --> 64
   100 / 16   =  6    4
   6   / 16   =  0    6

十进制小数转换为二进制小数

整数部分,直接转换为二进制即可

小数部分,使用"乘2取整,顺序排列"

  • 用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,直到积中的小数部分为零,或者达到所要求的精度为止
  • 然后把取出的整数部分按顺序排列起来, 即是小数部分二进制

最后将整数部分的二进制和小数部分的二进制合并起来, 即是一个二进制小数

// 整数部分(除2取余)
  12
/  2
------
   6    // 余0
/  2
------
   3    // 余0
/  2
------
   1   // 余1
/  2
------
  0   // 余1
//12 --> 1100
  
// 小数部分(乘2取整数积)
  0.125
*     2
  ------
   0.25  //0
   0.25
*     2
  ------
    0.5  //0
    0.5
*     2
  ------
    1.0  //1
    0.0
// 0.125 --> 0.001

// 12.8125 --> 1100.001

二进制小数转换为十进制小数

整数部分按照二进制转十进制即可

小数部分从最高位开始乘以2的负n次方, n从1开始

例如: 将 1100.001转换为十进制

// 整数部分(乘以2的n次方, n从0开始)
0 * 2^0 = 0
0 * 2^1 = 0
1 * 2^2 = 4
1 * 2^3 = 8
 // 1100 == 8 + 4 + 0 + 0 == 12

// 小数部分(乘以2的负n次方, n从0开始)
0 * (1/2) = 0
0 * (1/4) = 0
1 * (1/8) = 0.125
// .100 == 0 + 0 + 0.125 == 0.125

// 1100.001  --> 12.125

9.3原码反码补码

计算机只能识别0和1, 所以计算机中存储的数据都是以0和1的形式存储的

数据在计算机内部是以补码的形式储存的, 所有数据的运算都是以补码进行的

正数的原码、反码和补码

正数的原码、反码和补码都是它的二进制

负数的原码、反码和补码

  • 二进制的最高位我们称之为符号位, 最高位是0代表是一个正数, 最高位是1代表是一个负数
  • 一个负数的原码, 是将该负数的二进制最高位变为1
  • 一个负数的反码, 是将该数的原码除了符号位以外的其它位取反
  • 一个负数的补码, 就是它的反码 + 1

负数的原码、反码和补码逆向转换

  • 反码 = 补码-1
  • 原码= 反码最高位不变, 其它位取反

9.4位运算符

符号名称运算结果
&按位与同1为1
|按位或有1为1
^按位异或不同为1
~按位取反0变1,1变0
<<按位左移乘以2的n次方
>>按位右移除以2的n次方

按位异或

  • 相同整数相的结果是0。比如5^5=0
  • 多个整数相^的结果跟顺序无关。例如: 5^6^7=5^7^6
  • 同一个数异或另外一个数两次, 结果还是那个数。例如: 5^7^7= 5
9^5 = 12

 1001
^0101
------
 1100

按位取反

各二进位进行取反(0变1,1变0)

各二进位进行取反(0变1,1变0)

判断奇偶(按位或)

  •  偶数: 的二进制是以0结尾
  •  奇数: 的二进制是以1结尾
  • 任何数和1进行&操作,得到这个数的最低位

按位左移

把整数a的各二进位全部左移n位,高位丢弃,低位补0

  • 由于左移是丢弃最高位,0补最低位,所以符号位也会被丢弃,左移出来的结果值可能会改变正负性
  • 规律: 左移n位其实就是乘以2的n次方
2<<1; //相当于 2 *= 2 // 4
  0010
<<0100

2<<2; //相当于 2 *= 2^2; // 8
  0010
<<1000

按位右移

把整数a的各二进位全部右移n位,保持符号位不变

  • 为正数时, 符号位为0,最高位补0
  • 为负数时,符号位为1,最高位是补0或是补1(取决于编译系统的规定)

规律: 快速计算一个数除以2的n次方

2>>1; //相当于 2 /= 2 // 1
  0010
>>0001
4>>2; //相当于 4 /= 2^2 // 1
  0100
>>0001

10数组

10.1数组的基本概念

数组,从字面上看,就是一组数据的意思,没错,数组就是用来存储一组数据的

在C语言中,数组属于构造数据类型

数组的几个名词

  • 数组:一组相同数据类型数据的有序的集合
  • 数组元素: 构成数组的每一个数据。
  • 数组的下标: 数组元素位置的索引(从0开始)

数组的应用场景

一个int类型的变量能保存一个人的年龄,如果想保存整个班的年龄呢?

  • 第一种方法是定义很多个int类型的变量来存储
  • 第二种方法是只需要定义一个int类型的数组来存储

10.2定义数组

元素类型 数组名[元素个数];

// int 元素类型
// ages 数组名称
// [10] 元素个数
int ages[10];

10.3初始化数组

定义的同时初始化

指定元素个数,完全初始化

  • 其中在{ }中的各数据值即为各元素的初值,各值之间用逗号间隔
int ages[3] = {4, 6, 9};

不指定元素个数,完全初始化

  • 根据大括号中的元素的个数来确定数组的元素个数
int nums[] = {1,2,3,5,6};

指定元素个数,部分初始化

  • 没有显式初始化的元素,那么系统会自动将其初始化为0
int nums[10] = {1,2};

不指定元素个数,部分初始化

int nums[] = {[4] = 3};

先定义后初始化

int nums[3];
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;

没有初始化会怎样?

如果定义数组后,没有初始化,数组中是有值的,是随机的垃圾数,所以如果想要正确使用数组应该要进行初始化。

注意点

  • 使用数组时不能超出数组的索引范围使用, 索引从0开始, 到元素个数-1结束
  • 使用数组时不要随意使用未初始化的元素, 有可能是一个随机值
  • 对于数组来说, 只能在定义的同时初始化多个值, 不能先定义再初始化多个值
int ages[3];
ages = {4, 6, 9}; // 报错

10.4数组的使用

通过下标(索引)访问:

// 找到下标为0的元素, 赋值为10
ages[0]=10;
// 取出下标为2的元素保存的值
int a = ages[2];
printf("a = %d", a);

10.5数组的遍历

数组的遍历:遍历的意思就是有序地查看数组的每一个元素

    int ages[4] = {19, 22, 33, 13};
    for (int i = 0; i < 4; i++) {
        printf("ages[%d] = %d\n", i, ages[i]);
    }

10.6数组长度计算方法

因为数组在内存中占用的字节数取决于其存储的数据类型和数据的个数

  • 数组所占用存储空间 = 一个元素所占用存储空间 * 元素个数(数组长度)

所以计算数组长度可以使用如下方法

  • 数组的长度 = 数组占用的总字节数 / 数组元素占用的字节数
    int ages[4] = {19, 22, 33, 13};
    int length =  sizeof(ages)/sizeof(int);
    printf("length = %d", length);
输出结果: 4

10.7数组的越界问题

数组越界导致的问题

  • 约错对象
  • 程序崩溃
    char cs1[2] = {1, 2};
    char cs2[3] = {3, 4, 5};
    cs2[3] = 88; // 注意:这句访问到了不属于cs1的内存
    printf("cs1[0] = %d\n", cs1[0] );
输出结果: 88

10.8数组的注意事项

在定义数组的时候[]里面只能写整型常量或者是返回整型常量的表达式

 int ages4['A'] = {19, 22, 33};
 printf("ages4[0] = %d\n", ages4[0]);

  int ages5[5 + 5] = {19, 22, 33};
  printf("ages5[0] = %d\n", ages5[0]);

  int ages5['A' + 5] = {19, 22, 33};
  printf("ages5[0] = %d\n", ages5[0]);

错误写法

// 没有指定元素个数,错误
int a[];

// []中不能放变量
int number = 10;
int ages[number]; // 老版本的C语言规范不支持
printf("%d\n", ages[4]);

int number = 10;
int ages2[number] = {19, 22, 33} // 直接报错

// 只能在定义数组的时候进行一次性(全部赋值)的初始化
int ages3[5];
ages10 = {19, 22, 33};

// 一个长度为n的数组,最大下标为n-1, 下标范围:0~n-1
int ages4[4] = {19, 22, 33}
ages4[8]; // 数组角标越界

10.9数组名作为函数参数

  • 在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址,因此,当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递"
  • 实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元, 系统不再为形参数组分配存储单元
  • 既然两个数组共享一段内存单元, 所以形参数组修改时,实参数组也同时被修改了
     
void change2(int array[3])// int array = 0ffd1
{
    array[0] = 88;
}
int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    printf("ages[0] = %d", ages[0]);// 1
    change(ages);
    printf("ages[0] = %d", ages[0]);// 88
}

数组名作函数参数的注意点

  • 在函数形参表中,允许不给出形参数组的长度
void change(int array[])
{
    array[0] = 88;
}
  • 形参数组和实参数组的类型必须一致,否则将引起错误。
  • 当数组名作为函数参数时, 因为自动转换为了指针类型,所以在函数中无法动态计算除数组的元素个数

11排序算法

11.1计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,快于任何比较排序算法。

排序思路:

  • 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
  • 遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
  • 对额外空间内数据进行计算,得出每一个元素的正确位置;
  • 将待排序集合每一个元素移动到计算得出的正确位置上。

详细算法

先假设 20 个数列为:{9, 3, 5, 4, 9, 1, 2, 7, 8,1,3, 6, 5, 3, 4, 0, 10, 9, 7, 9}。

让我们先遍历这个无序的随机数组,找出最大值为 10 和最小值为 0。这样我们对应的计数范围将是 0 ~ 10。然后每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。

比如第一个整数是 9,那么数组下标为 9 的元素加 1,如下图所示。

 第二个整数是 3,那么数组下标为 3 的元素加 1,如下图所示。

继续遍历数列并修改数组......。最终,数列遍历完毕时,数组的状态如下图。

数组中的每一个值,代表了数列中对应整数的出现次数。

有了这个统计结果,排序就很简单了,直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次。比如统计结果中的 1 为 2,就是数列中有 2 个 1 的意思。这样我们就得到最终排序好的结果。

0, 1, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 9, 9, 10

int main()
{
    // 待排序数组
    int nums[5] = {3, 1, 2, 0, 3};
    // 用于排序数组
    int newNums[4] = {0};
    // 计算待排序数组长度
    int len = sizeof(nums) / sizeof(nums[0]);
    // 遍历待排序数组
    for(int i = 0; i < len; i++){
        // 取出待排序数组当前值
        int index = nums[i];
        // 将待排序数组当前值作为排序数组索引
        // 将用于排序数组对应索引原有值+1
        newNums[index] = newNums[index] +1;
    }
    
    // 计算待排序数组长度
    int len2 = sizeof(newNums) / sizeof(newNums[0]);
    // 输出排序数组索引, 就是排序之后结果
    for(int i = 0; i < len2; i++){
        for(int j = 0; j < newNums[i]; j++){
            printf("%i\n", i);
        }
    }
    /*
    // 计算待排序数组长度
    int len2 = sizeof(newNums) / sizeof(newNums[0]);
    // 还原排序结果到待排序数组
    for(int i = 0; i < len2; i++){
        int index = 0;
        for(int i = 0; i < len; i++){
            for(int j = 0; j < newNums[i]; j++){
                nums[index++] = i;
            }
        }
    }
    */
    return 0;
}

 11.2选择排序

 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。

  • 假设按照升序排序
  • 1.用第0个元素和后面所有元素依次比较
  • 2.判断第0个元素是否大于当前被比较元素, 一旦小于就交换位置
  • 3.第0个元素和后续所有元素比较完成后, 第0个元素就是最小值
  • 4.排除第0个元素, 用第1个元素重复1~3操作, 比较完成后第1个元素就是倒数第二小的值
  • 以此类推, 直到当前元素没有可比较的元素, 排序完成

// 选择排序
void selectSort(int numbers[], int length) {
    
    // 外循环为什么要-1?
    // 最后一位不用比较, 也没有下一位和它比较, 否则会出现错误访问
    for (int i = 0; i < length; i++) {
        for (int j = i; j < length - 1; j++) {
            // 1.用当前元素和后续所有元素比较
            if (numbers[i] < numbers[j + 1]) {
                //  2.一旦发现小于就交换位置
                swapEle(numbers, i, j + 1);
            }
        }
    }
}
// 交换两个元素的值, i/j需要交换的索引
void swapEle(int array[], int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

11.3冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复 地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

  •  假设按照升序排序
  • 1.从第0个元素开始, 每次都用相邻两个元素进行比较
  • 2.一旦发现后面一个元素小于前面一个元素就交换位置
  • 3.经过一轮比较之后最后一个元素就是最大值
  • 4.排除最后一个元素, 以此类推, 每次比较完成之后最大值都会出现再被比较所有元素的最后
  • 直到当前元素没有可比较的元素, 排序完成
// 冒泡排序
void bubbleSort(int numbers[], int length) {
    for (int i = 0; i < length; i++) {
        // -1防止`角标越界`: 访问到了不属于自己的索引
        for (int j = 0; j < length - i - 1; j++) {
           //  1.用当前元素和相邻元素比较
            if (numbers[j] < numbers[j + 1]) {
                //  2.一旦发现小于就交换位置
                swapEle(numbers, j, j + 1);
            }
        }
    }
}
// 交换两个元素的值, i/j需要交换的索引
void swapEle(int array[], int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

11.4插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  • 假设按照升序排序
  • 1.从索引为1的元素开始向前比较, 一旦前面一个元素大于自己就让前面的元素先后移动
  • 2.直到没有可比较元素或者前面的元素小于自己的时候, 就将自己插入到当前空出来的位置
int main()
{
    // 待排序数组
    int nums[5] = {3, 1, 2, 0, 3};
    // 0.计算待排序数组长度
    int len = sizeof(nums) / sizeof(nums[0]);

    //  1.从第一个元素开始依次取出所有用于比较元素
    for (int i = 1; i < len; i++)
    {
        // 2.取出用于比较元素
        int temp = nums[i];
        int j = i;
        while(j > 0){
            // 3.判断元素是否小于前一个元素
            if(temp < nums[j - 1]){
                // 4.让前一个元素向后移动一位
                nums[j] = nums[j - 1];
            }else{
                break;
            }
            j--;
        }
        // 5.将元素插入到空出来的位置
        nums[j] = temp;
    }
}
int main()
{
    // 待排序数组
    int nums[5] = {3, 1, 2, 0, 3};
    // 0.计算待排序数组长度
    int len = sizeof(nums) / sizeof(nums[0]);

    //  1.从第一个元素开始依次取出所有用于比较元素
    for (int i = 1; i < len; i++)
    {
        // 2.遍历取出前面元素进行比较
        for(int j = i; j > 0; j--)
        {
            // 3.如果前面一个元素大于当前元素,就交换位置
            if(nums[j-1] > nums[j]){
                int temp = nums[j];
                nums[j] = nums[j - 1];
                nums[j - 1] = temp;
            }else{
                break;
            }
        }
    }
}

11.5希尔排序

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

  • 1.希尔排序可以理解为插入排序的升级版, 先将待排序数组按照指定步长划分为几个小数组
  • 2.利用插入排序对小数组进行排序, 然后将几个排序的小数组重新合并为原始数组
  • 3.重复上述操作, 直到步长为1时,再利用插入排序排序即可
int main()
{
    // 待排序数组
    int nums[5] = {3, 1, 2, 0, 3};
    // 0.计算待排序数组长度
    int len = sizeof(nums) / sizeof(nums[0]);

// 2.计算步长
    int gap = len / 2;
    do{
        //  1.从第一个元素开始依次取出所有用于比较元素
        for (int i = gap; i < len; i++)
        {
            // 2.遍历取出前面元素进行比较
            int j = i;
            while((j - gap) >= 0)
            {
                printf("%i > %i\n", nums[j - gap], nums[j]);
                // 3.如果前面一个元素大于当前元素,就交换位置
                if(nums[j - gap] > nums[j]){
                    int temp = nums[j];
                    nums[j] = nums[j - gap];
                    nums[j - gap] = temp;
                }else{
                    break;
                }
                j--;
            }
        }
        // 每个小数组排序完成, 重新计算步长
        gap = gap / 2;
    }while(gap >= 1);
}

12二维数组

所谓二维数组就是一个一维数组的每个元素又被声明为一 维数组,从而构成二维数组. 可以说二维数组是特殊的一维数组。

示例:

  •     int a[2][3] = { {80,75,92}, {61,65,71}};
  •     可以看作由一维数组a[0]和一维数组a[1]组成,这两个一维数组都包含了3个int类型的元素

 12.1二维数组的定义

  • 数据类型 数组名[一维数组的个数][一维数组的元素个数]
  • 其中"一维数组的个数"表示当前二维数组中包含多少个一维数组
  • 其中"一维数组的元素个数"表示当前前二维数组中每个一维数组元素的个数

12.2二维数组的初始化

定义的同时初始化

int a[2][3]={ {80,75,92}, {61,65,71}};

先定义后初始化

int a[2][3];
a[0][0] = 80;
a[0][1] = 75;
a[0][2] = 92;
a[1][0] = 61;
a[1][1] = 65;
a[1][2] = 71;

按行分段赋值

int a[2][3]={ {80,75,92}, {61,65,71}};

按行连续赋值

int a[2][3]={ 80,75,92,61,65,71};

完全初始化,可以省略第一维的长度

int a[][3]={{1,2,3},{4,5,6}};
int a[][3]={1,2,3,4,5,6};

部分初始化,可以省略第一维的长度

int a[][3]={{1},{4,5}};
int a[][3]={1,2,3,4};

指定元素的初始化

int a[2][3]={[1][2]=10};
int a[2][3]={[1]={1,2,3}}

12.3二维数组的遍历

二维数组a[3][4],可分解为三个一维数组,其数组名分别为:

  • 这三个一维数组都有4个元素,例如:一维数组a[0]的 元素为a[0][0],a[0][1],a[0][2],a[0][3]。
  • 所以遍历二维数组无非就是先取出二维数组中得一维数组, 然后再从一维数组中取出每个元素的值
    char cs[2][3] = {
        {'a', 'b', 'c'},
        {'d', 'e', 'f'}
    };
    printf("%c", cs[0][0]);// 第一个[0]取出一维数组, 第二个[0]取出一维数组中对应的元素
    char cs[2][3] = {
        {'a', 'b', 'c'},
        {'d', 'e', 'f'}
    };
    for (int i = 0; i < 2; i++) { // 外循环取出一维数组
        // i
        for (int j = 0; j < 3; j++) {// 内循环取出一维数组的每个元素
            printf("%c", cs[i][j]);
        }
        printf("\n");
    }

13字符串

13.1字符串的基本概念

字符串是位于双引号中的字符序列

在内存中以“\0”结束,所占字节比实际多一个

 13.2字符串的初始化

在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串。

  • 当把一个字符串存入一个数组时,会把结束符‘\0’存入数组,并以此作为该字符串是否结束的标志。
  • 有了‘\0’标志后,就不必再用字符数组 的长度来判断字符串的长度了
    char name[9] = "lnj"; //在内存中以“\0”结束, \0ASCII码值是0
    char name1[9] = {'l','n','j','\0'};
    char name2[9] = {'l','n','j',0};
    // 当数组元素个数大于存储字符内容时, 未被初始化的部分默认值是0, 所以下面也可以看做是一个字符串
    char name3[9] = {'l','n','j'};

13.3字符串输出

  • 不必使用循环语句逐个地输入输出每个字符
  • 可以使用printf函数和scanf函数一次性输出输入一个字符数组中的字符串
  • 使用的格式字符串为“%s”,表示输入、输出的是一个字符串 字符串的输出
char chs[] = "lnj";
printf("%s\n", chs);

字符出输入输出函数

char ch[30];
gets(ch); // 输入:lnj
puts(ch); // 输出:lnj

13.4字符串长度和函数

利用sizeof字符串长度

因为字符串在内存中是逐个字符存储的,一个字符占用一个字节,所以字符串的结束符长度也是占用的内存单元的字节数

    char name[] = "it666";
    int size = sizeof(name);// 包含\0
    printf("size = %d\n", size); //输出结果:6

利用系统函数

  • 格式: strlen(字符数组名)
  • 功能:测字符串的实际长度(不含字符串结束标志‘\0’)并作为函数返回值。
    char name[] = "it666";
    size_t len = strlen(name2);
    printf("len = %lu\n", len); //输出结果:5

字符串连接函数:strcat

  • 格式: strcat(字符数组名1,字符数组名2)
  • 功能:把字符数组2中的字符串连接到字符数组1 中字符串的后面,并删去字符串1后的串标志 “\0”。本函数返回值是字符数组1的首地址。
char oldStr[100] = "welcome to";
char newStr[20] = " lnj";
strcat(oldStr, newStr);
puts(oldStr); //输出: welcome to lnj"

字符串拷贝函数:strcpy

- 格式: strcpy(字符数组名1,字符数组名2)- 功能:把字符数组2中的字符串拷贝到字符数组1中。串结束标志“\0”也一同拷贝。字符数名2, 也可以是一个字符串常量。这时相当于把一个字符串赋予一个字符数组。

char oldStr[100] = "welcome to";
char newStr[50] = " lnj";
strcpy(oldStr, newStr);
puts(oldStr); // 输出结果:  lnj // 原有数据会被覆盖

字符串比较函数:strcmp

  • 格式: strcmp(字符数组名1,字符数组名2)
  • 功能:按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果。
    • 字符串1=字符串2,返回值=0;
    • 字符串1>字符串2,返回值>0;
    • 字符串1<字符串2,返回值<0。
    char oldStr[100] = "0";
    char newStr[50] = "1";
    printf("%d", strcmp(oldStr, newStr)); //输出结果:-1
    char oldStr[100] = "1";
    char newStr[50] = "1";
    printf("%d", strcmp(oldStr, newStr));  //输出结果:0
    char oldStr[100] = "1";
    char newStr[50] = "0";
    printf("%d", strcmp(oldStr, newStr)); //输出结果:1

13.4字符串数组基本概念

字符串数组其实就是定义一个数组保存所有的字符串

  • 1.一维字符数组中存放一个字符串,比如一个名字char name[20] = “nj”
  • 2.如果要存储多个字符串,比如一个班所有学生的名字,则需要二维字符数组,char names[15][20]可以存放15个学生的姓名(假设姓名不超过20字符)
  • 如果要存储两个班的学生姓名,那么可以用三维字符数组char names[2][15][20]

字符串数组的初始化

char names[2][10] = { {'l','n','j','\0'}, {'l','y','h','\0'} };
char names2[2][10] = { {"lnj"}, {"lyh"} };
char names3[2][10] = { "lnj", "lyh" };

14指针

14.1指针基本概念

什么是地址?

生活中的地址:

内存地址: 

 地址与内存单元中的数据是两个完全不同的概念

  • 地址如同房间编号, 根据这个编号我们可以找到对应的房间
  • 内存单元如同房间, 房间是专门用于存储数据的

变量地址:

系统分配给"变量"的"内存单元"的起始地址

int num = 6; // 占用4个字节
//那么变量num的地址为: 0ff06

char c = 'a'; // 占用1个字节
//那么变量c的地址为:0ff05

 什么是指针?

  • 在计算机中所有数据都存储在内存单元中,而每个内存单元都有一个对应的地址, 只要通过这个地址就能找到对应单元中存储的数据.

  • 由于通过地址能找到所需的变量单元,所以我们说该地址指向了该变量单元。将地址形象化的称为“指针”

  • 内存单元的指针(地址)和内存单元的内容是两个不同的概念。

一张银行卡的账户就是内存地址,我们根据银行卡账号就能找到里面存储的数据

银行卡里面的钱就是实际内容

什么是指针变量?

在C语言中,允许用一个变量来存放其它变量的地址, 这种专门用于存储其它变量地址的变量, 我们称之为指针变量

14.2定义指针变量的格式

指针变量的定义包括两个内容:

  • 指针类型说明,即定义变量为一个指针变量;
  • 指针变量名;
char ch = 'a';
char *p; // 一个用于指向字符型变量的指针
p = &ch;  
int num = 666;
int *q; // 一个用于指向整型变量的指针
q = &num;  

14.3指针变量的初始化方法

指针变量初始化的方法有两种:定义的同时进行初始化和先定义后初始化

  • 定义的同时进行初始化
int a = 5;
int *p = &a;
  • 先定义后初始化
int a = 5;
int *p;
p=&a;
  • 把指针初始化为NULL
int *p=NULL;
int *q=0;

14.4访问指针所指向的存储空间

  • C语言中提供了地址运算符&来表示变量的地址。其一般形式为:
    • &变量名;
  • C语言中提供了*来定义指针变量和访问指针变量指向的内存存储空间
    • 在定义变量的时候 * 是一个类型说明符,说明定义的这个变量是一个指针变量
  • 在不是定义变量的时候 *是一个操作符,代表访问指针所指向存储空间
int a = 5;
int *p = &a;
printf("a = %d", *p); // 访问指针变量

14.5指针类型

虽然在同一种编译器下, 所有指针占用的内存空间是一样的,但不同类型的变量却占不同的字节数

  •  一个int占用4个字节,一个char占用1个字节,而一个double占用8字节;
  • 现在只有一个地址,我怎么才能知道要从这个地址开始向后访问多少个字节的存储空间呢,是4个,是1个,还是8个。
  • 所以指针变量需要它所指向的数据类型告诉它要访问多少个字节存储空间

14.6二级指针

  • 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针
    char c = 'a';
    char *cp;
    cp = &c;
    char **cp2;
    cp2 = &cp;
    printf("c = %c", **cp2);

 14.7数组指针的概念及定义

数组元素指针

  • 一个变量有地址,一个数组包含若干元素,每个数组元素也有相应的地址, 指针变量也可以保存数组元素的地址
  • 只要一个指针变量保存了数组元素的地址, 我们就称之为数组元素指针

 14.8指针访问数组元素

    int main (void)
{
      int a[5] = {2, 4, 6, 8, 22};
      int *p;
      // p = &(a[0]); 
      p = a;
      printf(“%d %d\n”,a[0],*p); // 输出结果: 2, 2
}

在指针指向数组元素时,允许以下运算:

  • 加一个整数(用+或+=),如p+1
  • 减一个整数(用-或-=),如p-1
  • 自加运算,如p++,++p
  • 自减运算,如p–,--p

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值