C语言笔记

前言

基于C语言学习整理的笔记

BUG  debug Tip  Question

C语言学习的重点

Linux操作系统使用、数据类型、常量和变量、运算符、输入输出、控制语句、数组、指针、函数、结构体、共同体、内存管理

一 程序设计入门

1 Helloword

1.1 第一个程序

#include<stdio.h>//头文件
int main()//主函数(唯一)main为程序的入口
{
    printf("hello world!\n");//\n为换行
    return 0;//return 0 为程序的出口
}

1.2  简单计算

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

1.3 库函数

方便代码的编写而提供的一系列现成的函数

 库函数链接:https://cplusplus.com/reference/clibrary/

1.3.1 printf()

printf()的作用是将参数文本输出到屏幕。

tips:

printf()不会在行尾自动添加换行符,运行结束后,光标就停留在输出结束的地方,不会自动换行。

1.3.1.1 限定宽度 

printf()允许限定占位符的最小宽度

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>  

int main() {
	printf("%5d\n", 123);
	return 0;
}
  123

 示例中,%5d表示这个占位符的宽度至少为5位。如果不满5位,对应的值的前面会添加空格。

输出的值默认是右对齐,如果想让左对齐,可以在%后面添加负号“-”,这样空格会出现在内容的右边。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>  

int main() {
	printf("%-5d\n", 123);
	return 0;
}

 对于小数,限定符会限制所有数字的最小显示宽度。

#include <stdio.h>  

int main() {
	printf("%12f\n", 123.456);
	return 0;
}

 示例中,%12f表示输出的浮点数最少要占据12位。由于小数的默认显示精度是小数点后6位,所以123.456的输出结果会添加2个空格

  123.456000
 1.3.1.2 正负号的显示 

       默认情况下,printf()不对正数显示+号,只对负数显示-号,如果想让正数显示正好,可以在占位符的%后面加一个+。

#include <stdio.h>  

int main() {
	printf("%+d\n", 12);        //+12
	printf("%+d\n", -12);       //-12
	return 0;
}
  1.3.1.3 限制小数点的位数 

        输出小数时,有时希望限定小数的位数。举例来说,希望小数点后面只保留三位,占位符可以写成%.3f。

#include <stdio.h>  

int main() {
	printf("%.3f\n", 0.5);
	printf("%.3f\n", 0.54321);
	return 0;
}
0.500
0.543

 限制小数点位数可以和占位符宽度限制相结合

#include <stdio.h>  

int main() {
	printf("%8.2f\n", 0.54321);          //[][][][][]0.54
	printf("%*.*f\n", 8,2,0.54321);      //[][][][][]0.54
	return 0;
}
1.3.1.4 输出部分字符串 

        %s 占位符用来输出字符串,默认是全部输出。如果只想输出开头的部分,可以用%[m].[n]指定至少输出的位数和输出字符串的长度,其中[m]代表一个数字,至少输出的位数,[n]也代表一个数字,表示该字符串输出几位。

#include <stdio.h>  

int main() {
	printf("%+8.2s\n", "123456789");		//[][][][][][]12
	printf("%-8.2s\n", "123456789");		//12[][][][][][]
	return 0;
}

 1.3.2 scanf()

        scanf()用于读取用户键盘输入,程序运行到这个语句的时候会停下来等待用户的输入,用户输入数据后,按下回车键

1.3.2.1 scanf()在vs的安全问题

        vs编译器十分严谨,scanf()本身也有一些问题,但是考虑代码的跨平台性,在这里依然使用scanf(),但是使用scanf()在vs中会报错,所以必须在代码的第一行加上这样一行代码

#define _CRT_SECURE_NO_WARNINGS 1
 1.3.2.2 scanf()的基本用法

        ①scanf()可以一次输入多个值

int main() {
	int a = 0;
	int b = 0;
	float c = 0.0;
	float d = 0.0;
	scanf("%d %d %f %f", &a,&b,&c,&d);
	printf("%d %d %f %f",a,b,c,d);
	return 0;
}
4
3
4.3e3
5
4 3 4300.000000 5.000000

        ② scanf()读取一行数/[][][]-15.45e12#[]0/

#include <stdio.h>  

int main() {
	int a = 0;
	float b = 0.0;			//输入/    -15.45e12# 0
	scanf("%d  %f", &a,&b);
	printf("%d %e",a,b);
	return 0;
}
    -15.45e12# 0
-15 4.500000e+11

        当scanf()读取到这一行字符的时候,整数占位符可以认识-15,但是小数点整数占位符不认识,因此变量a中会记录-15,到第二个科学计数占位符它是可以认识小数点,0.45e12最终会被科学计数为4.500000e+11,即为4.5*10^11,而#占位符%e并不认识,所以会在#处停止读入。

         ③ scanf()读取是有返回值的

        scanf()是有一个int类型的返回值,其中0表示没有读取到任何项或者匹配失败;如果读取到文件结尾,则会返回常量EOF,用-1表示;如果是其他整数,读到几个数就会返回整数几。

 1.3.2.3 scanf()的占位符
%[]在括号中指定一组匹配字符(比如%[0-9]),遇到不在集合中的字符匹配将停止
%c字符
%s从第一个空白字符读起直到遇到空白字符

        以上这些占位符中,除了%c意外,其余都会忽略起首的空格字符,%c不会忽略,总是会返回第一个字符,无论这个字符是否为空,如果一定要忽略,可以将函数写成scanf(" %c",&a),即主动给占位符前面放上一个空格,这样函数会把输入信息前面的空格都会跳过去

        scanf()将字符串读入字符数组时,不会检测字符串是否超过了字符的数组长度。所以储存字符串的时候很可能超过数组的边界,导致预想不到的结果,因此在使用%s的时候需要指定读入字符串的长度即%[m]s

 1.3.2.4 赋值忽略符“*”

        赋值忽略符是为了避免输入的信息与预定格式不符,而在占位符的%后加上*号,这样在解析这个占位符上的字符后就会丢掉

int main() {
	int year = 0;
	int month = 0;			
	int day = 0;
	//1999-09-09
	scanf("%d%*c%d%*c%d", &year,&month,&day);
	printf("%d %d %d",year,month,day);
	return 0;
}
1997
07
05
1997 7 5

1.3.3 getchar()和putchar()

        getchar()函数返回用户从键盘输入的一个字符,使用时不带有任何参数。程序运行到这个命令就会暂停,等待用户的键盘输入,等同于使用scanf()方法读取一个字符。

int main() {
	int ch;
	ch = getchar();    //等同于scanf("%c",&ch)
	printf("%d\n", ch);
	printf("%c\n", ch);
	return 0;
}
//输入
K
//输出
75
K

         getchar()函数不会忽略起首的空白字符,总是返回当前读取的第一个字符,无论是否为空格,如果读取失败,返回常量EOF,由于EOF通常是-1,所以返回值的类型要设为int,而不是char。

        EOF:代表文件结束标志(end of file)

1.3.3.1 getchar()的作用

①跳过某一行

int main() {
	while (getchar() != '\n')  //这里的换行符一定是单引号字符不是双引号,不然就会报错“类型不匹配”
		;
	return 0;
}

         直到getchar()等于‘\n’才跳出循环,‘\n’代表换行,如果不到换行,getchar()读一个丢一个。

②统计某一行字符长度

int main() {
	int len = 0;
	while (getchar() != '\n')
		len++;;
	return 0;
}

③跳过空格字符

int main() {
	int ch;
	while ((ch = getchar()) == ' ');
	printf("%c", ch);
}
         u
u
 1.3.3.2 putchar()的作用

        用来打印单个字符,相当于printf()

int main() {
	int ch;
	ch = getchar();
	//printf("%c", ch);
	putchar(ch);
}

       putchar()如果操作成功会返回打印的字符,如果打印失败则会返回EOF。

1.4 块和作用域

1.4.1 代码块

C语言中成对大括号构成代码叫程序块(也叫复合语句)

 一个函数是一个代码块,一个for循环也叫一个代码块

 1.4.2 作用域

作用域(scope)指的是变量的生效范围

C语言的变量作用域主要有两种:文件作用域(file scope)和块作用域(block scope)。

  1.4.2.1  块作用域

        在程序块中声明的名称,只在该程序块中通用,在其他区域都无效。也就是说,变量的名称从变量声明的位置开始,到包含该声明的程序块最后的大括号为止这一区间内通用。这样的作用域称为块作用域

        块作用域指的是由大括号组成的代码块,他形成一个单独的作用域。凡是在块作用域里面声明的变量,只在当前代码块有效,代码块外不可见。

        块作用域一般针对的是局部变量

①代码块可以嵌套

int main() {
	int a = 12;
	if (a == 12) {
		int b = 99;
		printf(" %d % d\n",a,b);
	}
	return 0;
}

         嵌套的代码块中最内部代码块中可以使用外部代码块中的变量,而外部代码块不能使用内部代码块的变量。

②局部优先

        当内外代码块中的变量同名的时候,内部代码块中的变量会覆盖掉外部代码块中的变量。

int main() {
	int a = 10;
	{
		int a = 20;
		printf("Inter(a) = %d\n", a);
	}
	printf("Outer(a) = %d", a);
	return 0;
}
Inter(a) = 20
Outer(a) = 10

③for循环也是作用域

int main() {
	for (int i = 0; i < 5; i++) {
		printf("%d", i);
	}
	printf("%d", i);//程序会报错,因为i定义在循环内部,循环外无法使用
	return 0;
}
1.4.2.2 文件作用域 

        文件作用域(file scope)指的是在函数的外部声明的变量(全局变量),从声明的位置到文件结束都有效,通俗的将就是全局变量就是有文件作用域的。

int i = 7;
void test() {
	printf("i = %d", i);
}
int main() {
	printf("i = %d",i);
	return 0;
}
i = 7
i = 7

         全局变量总声明位置开始的整个当前文件都是他的作用域,可以在任何范围内读取这个变量,甚至其他源文件中也可以读取。

1.5 C语言关键字

        C语言中有一批保留的名字的符号,这些符号被称为保留字或者关键字,都具有特殊的含义,是保留给C语言使用的。程序员在创建C标识符的时候不能和关键字重复。

        C语言的32个关键字如下:

break auto case char const continue default do double else enum extern return 
short signed sizeof static float for goto if int long register struct switch 
typedef union unsigned void volatile while

C语言关键字的详细介绍:C 关键词 - cppreference.com

         注:在C99标准中加入了inline、restrict、_Bool、_Complpex、_Imaginary等关键字,        不过使用最多的还是上面的32个关键字。

        C语言关键字的分类:

存储类型相关关键字(4):

auto:基本描述的是局部变量,存储在栈区
static:描述的是静态变量,存储在静态区
register:存储在寄存器里面,是一种建议性的关键字
extern:

数据类型相关(14): 

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

控制语句相关(12): 

if else switch case default for while do break continue goto return 

 说明符(2):

const volatile

1.5.1 关键字之sizeof()

 sizeof()是C语言提供的一种运算符(操作符),也是一个关键字。

主要有以下两种形式:

sizeof(类型)
sizeof 表达式


int a = 5;
sizeof(int);    //sizeof(类型)
sizeof(a);      //sizeof 表达式

        sizeof()返回某种数据类型或某个值占用的字节数量,他的参数可以是数据类型的关键字,也可以是变量名或者某个具体的值。

        sizeof不仅可以计算内置的类型的大小,计算数组、自定义类型的大小都是可以的

        默认小数类型都是double类型,占4个字节,当给小数后面带上f(3.14f),那这个小数将是float类型 ,那这个小数将是

②sizeof的返回值类型

        sizeof 运算符的返回值,C语言只规定是无符号整数,并没有规定具体的类型,而是留给系统自己去决定sizeof 到底返回什么类型。不同的系统中,返回值的类型有可能是 unsigned int ,也有可能是 unsigned1ong,甚至是unsigned long long,对应的 printf()占位符分别是%u、%1u和 %11u。这样不利于程序的可移植性。
        C语言提供了一个解决方法,创造了一个类型别名 size_t,用来统一表示 sizeof 的返回值类型。该别名定义在stddef.h头文件(引入 stdio.h时会自动引入)里面,对应当前系统的 sizeof 的返回值类型,可能是unsigned int,也可能是unsigned long 。                

        注:VS2022中 sizg_t是定义在vcruntime.h 中的,不同的编译器实现上略有差异的。

        C语言还提供了一个常量 SIZE_MAX ,表示 size_t 可以表示的最大整数,所以,size_t能够表示的整数范围为[0,SIZE_MAX]。printf()有专门的占位符%zd或%zuI用来处理 size_t 类型的值。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>  

int main() {

	printf("%d", sizeof(int));      

	return 0;
}

         上面代码中,不管 sizeof 返回值的类型是什么,%zd 占位符(或 %zu )都可以正确输出。
        如果当前系统不支持%zd或%zu,可使用%u(unsigned int)或%lu(unsigned long int) 代替。

1.5.2 关键字之signed和unsigned

         生活中的我们发现有的数据只有正数,比如:年龄,体重等,但是有些数据是有正数和负数的,比如:温度,海拔
        C语言引入和 signed和unsigned 关键字来修饰 char、short、int、long 等整型家族的类型:

        使用 signed 关键字,表示一个类型带有正负号,包含负值;

        使用 unsigned 关键字,表示该类型不带有正负号,只能表示零和正整数。 

	char
	[signed] char 
	unsigned char
	
	//短整形
	short [int]
	[signed] short [int]
	unsigned short [int]

	//整形
	int
	[signed] int
	unsigned int

	//长整形
	long [int]
	[signed] long [int]
	unsigned long [int]

	//更长整形
	//C99中引入
	long long [int]
	[signed] long long [int]
	unsigned long long [int]

1.6 C语言语句的分类

1.6.1 空语句

空语句:语句中只有一个分号

空语句的应用场景:某段代码中需要一条语句,但是这条代码不需要做任何事情

1.6.2 表达式语句

表达式后面加上了分号构成了表达式语句

1.6.3 函数调用语句

1.6.4 复合语句

成对大括号中的语句,也叫代码块

1.6.5 控制语句

        控制语句用于控制程序的执行流程,以实现程序的各种结构方式(C语言支持三种结构:顺序结构、选择结构、循环结构),它们由特定的语句定义符组成,C语言有九种控制语句。可分成以下三类:
1.条件判断语句也叫分支语句:if语句、switch语句;
2.循环执行语句:do while语句、while语句、for语句;
3.转向语句:break语句、goto语句、continue语句、return语句

2 计算

2.1 一些简单的概念

2.1.1 计算

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

2.1.2 算法

计算的步骤

2.1.3 编译与解释的区别

把代码A编译成代码B,一边解释一边执行

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

编译:借助一个程序,这个程序将你的程序翻译成机器语言,然后计算机按照机器语言执行程序。

2.1.4 编程语言热度网址

Home - TIOBE

2.1.5 输入

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

int inputAndOutput() {
	//输入一个整数
	int a;
	scanf("%d", &a);
	//输出一个整数
	printf("%d\n",a);
	//输入一个小数
	double b;
	scanf("%lf", &b);
	//输出一个小数
	printf("%f",b);
}
5
5
0.5
0.500000

 2.1.6 输出

2.1.6.1 格式控制符对照表
格式控制符
%d对应类型int的整数,十进制整数
%ld对应类型long的整数
%x十六进制整数
%p输出地址,以整数形式输出指针
%lu

unsigned long int 类型

%a十六进制浮点数,字母输出为小写
%A十六进制浮点数,字母输出为大写
%c字符
%e使用科学计数法的浮点数,指数部分的e为小写
%E使用科学计数法的浮点数,指数部分的E为大写
%i整数,基本等同于%d
%f小数(包含float类型和double类型)
%g6个有效数字的浮点数。整数部分一旦超过6位,就会自动转换为科学计数法,指数部分的e为小写
%G等同于%g,唯一的区别是指数部分的E为大写
%hd十进制short int 类型
%ho八进制short int 类型
%hx十六进制 short int 类型
%huunsigned short int类型
%ld十进制long int 类型
%lo八进制long int 类型
%lx十六进制long int 类型
%lld十进制long long int 类型
%llo八进制long long int 类型
%llx十六进制long long int 类型
%lluunsigned long long int 类型
%Le科学计数法表示的long double 类型浮点数
%Lflong double 类型浮点数
%n已输出的字符串数量。该占位符本身不输出,只将值储存在指定变量中
%o八进制整数
%s字符串
%u无符号整数(unsigned int)
%zdsize_t类型
 2.1.6.2 转义字符对照表
转义字符对照表
\t跳到下一个Tab位

2.2 变量

2.2.1 变量的概念

变量就是一个存储数据的地方

标识符:给变量起的名字

标识符的书写要求:字母、数字、下划线组成,数字不能在第一位上并且不能是C语言的关键字 

2.2.2 变量的定义形式

<类型名称><变量名称>;

int price;

int amount;

int price,amount;

2.2.3 赋值和初始化

赋值发生在定义变量的时候

tips:

①变量在使用之前必须赋值/初始化

②变量中得值可以被修改 

2.3 数据类型

2.3.1 占位符

整数小数
intdouble
printf("%d",...)printf("%f",...)
scanf("%d",...)scanf("%lf",...)

2.3.2 定点数与浮点数

定点数和浮点数

定点数:机器中所有数据的小数点的位置是固定的,通常数据表示的是纯小数或纯整数。

浮点数:机器中数据的小数点是不固定的。

float
double
long double
double的类型要比float的精度要高一些

 2.3.3 char 字符

char
[signed] char
unsigned char

注意,C 语言规定 char 类型有可能是signed char,也有可能是 unsigned char 。
这一点与 int 不同,int 就是等同于 signed int

  2.3.4 int整型

//短整型
short [int] 
[signed] short [int]
unsigned short [int]

//整形
int 
[signed] int        //输入输出用%d
unsigned int        //输入输出用%u

//长整型
long [int]
[signed] long [int]
unsigned long [int]

//常常整形
//C99中引用
[signed] long long [int]
unsigned long long [int]

unsigned和signed的区别:

因为数据是有正负之分的,unsigned表示无符号的,signed表示有符号的。

signed可以表示正数也可以表示负数。

unsigned修饰的变量中只能表示正数。 

2.3.5 布尔类型 

在C99中也引进了布尔类型,专门表示真假的

1_Bool
#define bool _Bool

#define false 0
#define true 1

布尔类型的使用得包含头文件<stdbool.h>

布尔类型变量得取值是:true或者false 

2.4 表达式

2.4.1 表达式的概念                                                

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

2.4.2 运算符(operator)/操作符

进行运算的动作

 2.4.2.1 操作符的分类

①算术操作符:+         -        *         /        %

②移位操作符:右移操作符>>       左移操作符<<

int main() {
	int a = 10;
	int b = a >> 1;//>> 右移操作符,移动的是二进制的位,存储在内存中的补码的二进制位
	return 0;
}

③位操作符:按位与&        按位或|        按位异或^

位操作符也是针对二进制位进行计算的

④赋值操作符:

+=        -=       *=        /=        %=        <<=        >>=        &=        |=        ^=

⑤单目操作符:只有一个操作数

!            逻辑反操作
-             负值
+             正值
&             取地址
sizeof        操作数的类型长度(以字节位单位)
~             对一个数的二进制按位取反
--            前置、后置--
++            前置、后置++
*             间接访问操作符(解引用操作符)
(类型)        强制类型转换

⑥关系操作符

> >= < <= != ==

 ⑦逻辑操作符

&& ||

⑧条件操作符(三目操作符 )

? :     例子:a>b?a:b;

 ⑨逗号操作符

,    例如:a,b,c;

⑩下标引用

[],    例如:a[b]

⑪函数调用

(),    函数的调用,例如fun()

⑫结构成员:

.->    例如:a.b    a->b

操作符参考文档:表达式 - cppreference.com 

2.4.2.1 除号 / 

①在整数除法中,除号两端都是整数,进行的是整数除法

int main() {
	int a = 6/4;
	printf("%d",a);
	return 0;
}
1

除号两边只要有一个小数,结果就为小数,但是得用float接收

int main() {
	float a = 6.0/4;
	float b = 8/4.0;
	printf("%f\n", a);
	printf("%f\n",b);
	return 0;
}
1.500000
2.000000

 ②应用

int main() {
	int score = 5;
	score = (score / 25.0) * 100;//将计算的小数结果赋值给整数类型变量,会丢失精度
	printf("%d", score);
	return 0;
}
20
 2.4.2.2 取模%

tips:取模运算的运算符两边只能是整数类型

①负数求模的规则是,结果的正负号由第一个运算符的正负号决定 

int main() {
	printf("%d\n", 7 % -5);
	printf("%d\n",-7 % -5);
	printf("%d\n",-7 % 5);
	return 0;
}

运行结果:
2
-2
-2

2.4.3 算子(operand)

参与运算的值,可能是常量也可能是变量

2.4.4 运算符的优先级

优先级和结合性的参考链接:C 运算符优先级 - cppreference.com

优先级运算符运算结合关系举例
1+单目不变自右向左a*+b
1单目取负自右向左a*-b
2*自左向右a*b
2/自左向右a/b
2%取余自左向右a%b
3+自左向右a+b
3自左向右a-b
4=赋值自右向左a=b

2.4.5 特殊的

对于C语言而说,赋值是一个运算符不是一个特殊的语句

2.4.6 交换变量 

int temporaryVariable() {
	int a = 5;
	int b = 6;
	int t = a;
	 a = b;
	 b = t;
	printf("a=%d,b=%d\n", a, b);
}

 2.4.7 复合赋值和递增递减

2.4.7.1 复合赋值

5个算术运算符,+ - * / %,可以和赋值运算符“=”结合起来,形成复合赋值运算符“+=” “-=” “*=” “/=” 和 “%=”
● total += 5;
● total = total + 5:
注意两个运算符中间不要有空格

2.4.7.2 递加递减 

        “++”和“--”是两个很特殊的运算符,它们是单目运算符,这个算子还必须是变量。这两个运算符分别叫做递增和递减运算符他们的作用就是给这个变量+1或者-1。

        单目运算符和双目运算符:

        单目运算符指操作数只有一个的运算,双目运算符指运算符有两个操作数的运算。
● count++;
● count +=1;

● count = count +1;

int aPlus(){
	int a = 10;
	int b = 10;
	printf("a++ = %d\n",a++);    //a++作为一个表达式它的结果是a加一以前的值
	printf("a = %d\n",a);
	printf("++b = %d\n",++b);
	printf("b = %d\n",b);
}
a++ = 10
a = 11
++b = 11
b = 11
int aSubtract(){
	int c = 10;
	int d = 10;
	printf("c-- = %d\n", c--);
	printf("c = %d\n", c);
	printf("--d = %d\n", --d);
	printf("d = %d\n", d);
}
c-- = 10
c = 9
--d = 9
d = 9

3 循环和判断

3.1 判断

3.1.1 关系运算

 计算两个值之间的关系,叫做关系运算

运算符

含义

= =是否相等
! = 不等于
>大于
>=大于等于
<小于
<=小于等于
关系运算的结果的只有0和1,当结果符合预期,值为1,当结果不符合预期结果为0
int relationOperation() {
	printf("%d\n",5==3);
	printf("%d\n",4>=7);
	printf("%d\n",6<9);
};

3.1.2 关系运算的优先级 

关系运算符的优先级比算数运算符低,但是比赋值运算符高 

==和!=的优先级要比><的优先级低

6>5>4        这段运算首先会判断6与5的大小关系得到期望,值为1;进而判断1与4的大小关系 ,未得到期望,值为0;所以6>5>4最终值为0

3.1.3 注释(comment)

//单行注释
/*多行注释*/

多行注释也可用于一行内 

注释的替换        //拓展

语句前的注释:替换成等长空格;

语句中的注释:替换成一个空格;

语句末的注释:直接删除。 

注:注释不能放在双引号里

 3.1.4 if-else条件判断语句 

为什么强调if和else后面要用{}

加{}可以执行括号内的语句,没加只能执行if和else后的第一条语句

int score() {
	const  int STANDARD = 60;
	int score = 0;
	printf("请输入你的分数:\n");
	scanf("%d",&score);
	printf("你的分数为%d,", score);
	if (score >= STANDARD)
		printf("恭喜您通过了本次测试!");
	else
	{
		printf("很遗憾本次测试未通过!");
	}
}

3.2 循环

3.2.1 while

        while语句是一个循环语句,它会首先判断一个条件是否满足,如果条件满足,则执行后面紧跟着的语句或语句括号,然后再次判断条件是否满足,如果条件满足则再次执行,直到条件不满足为止。后面紧跟的语句或语句括号,就是循环体。

int digitCapacityCirculate() {
	int x;
	int n = 0;
	printf("请输入一个数:\n");
	scanf("%d", &x);
	x /= 10;
	n++;
	while (x > 0) {
		x /= 10;
		n++;
	}
	printf("您输入的这个数是一个%d位数", n);
}

3.2.2 do-while 

do-whie循环和whie循环很像,唯一的区别是我们在循环体执行结束的时候才来判断条件。也就是说,无论如何,循环都会执行至少一遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时结束循环。

int digitCapacityDoWhile() {
	int x;
	int n = 0;
	printf("请输入一个数:\n");
	scanf("%d", &x);
	do {
		x /= 10;
		n++;
	} while (x > 0);
	printf("您输入的这个数是%d位数!",n);

}

 3.2.3 for循环

for(初始动作;循环继续条件; 每轮的动作){

}

for中的每一个表达式都是可以省略的

for(;条件;)== while(条件)

int factorialFor() {
	int n = 0;
	int i = 1;
	int fact = 1;
	printf("需要计算多少的阶乘:\n");
	scanf("%d", &n);
	for (i = 1; i <= n;i++) {
		fact *= i;
	}
	printf("%d!=%d", n, fact);
}
//5! = 125;
for (int i = 10; i > 1;i /=2) {
	printf("%d", i++);
}
/*
解析:
i=10,输出10,执行i++,i变成11
11/2截取整数部分,是5,输出5,执行i++,i变成6
6/2=3,输出3,执行i++,i变成4
4/2=2,输出2,执行i++,i变成3
3/2截取整数部分,是1,不满足i>1的循环条件,退出循环

因此输出结果是10 5 3 2*/
int forI2() {
	printf("forI2\n");
	int i = 1;
	for (i = 0; i++ < 10;) {
		printf("i=%d\n", i);
	}
}
/*
forI2
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
*/

3.2.4 三种循环的区别

●如果有固定次数,用for
●需要先执行后判断的,用do_while
●不清楚执行几次的用while

4 进一步的循环和判断

4.1 逻辑类型和运算

4.1.1 逻辑类型

 布尔类型

#include<stdbool.h>

4.1.2 逻辑运算

●逻辑运算是对逻辑量进行的运算,结果只有0和1

●逻辑量是关系运算或逻辑运算的结果

逻辑运算符

运算符描述示例
逻辑非!a
&&逻辑与a && b
||逻辑或a || b

优先级        !> && > ||

短路        对于&&和||,两者具有短路功能,如果关系式左边成立则右边不进行判断 

4.1.3 条件运算和逗号运算

1  条件运算符

count =(count >20)?count -10 : count+10;

条件运算符的优先级高于赋值运算符,但低于其他运算符;

条件运算符是自右向左结合        w<x?x+ w :x<y?x:y

BUG: visual studio2022的i = 3+4,5+6;不加括号会报错。

2  逗号运算符 

●逗号运算符用来连接两个表达式,并且用右边的值作为它的结果

●逗号的优先级是所有表达式中最低的,所以它两边的表达式会先计算,

int main(){
    int i;
    i = 3+4,5+6;
    printf("i=%d",i)
}
//i=7
//visual studio2022无法运行
int main(){
    int i;
    i = (3+4,5+6);
    printf("i=%d",i)
}
//i=11

 逗号表达式的用途

for(i=0,j=10;i<j;i++,j--){...}

4.2 联级和嵌套的判断

4.2.1 嵌套的if-else

        if条件执行的代码中又含有if条件判断语句

int sizeComparison() {
	int a, b, c;
	scanf("%d %d %d,", &a, &b, &c);
	int max = 0;
	if (a>b) {
		if (a>c) {
			max = a;
		}
		else {
			max = c;
		}
	}
	else {
		if (b>c) {
			max = b;
		}
		else {
			max = c;
		}
	}
	printf("max = %d", max);
}

4.2.2 级联的if-else

类似于分段函数

int cascade(){
    int x = 0;
    scanf("%d",&x)
    int f = 0;
    if(x<0){
        f = -1;
    }else if(x == 0){
        f = 0;
    }else if(x > 5){
        f = 2 * x;
    }else {
        f = 3 * x;
    }
    printf("%d\n",f);
    return 0
}

4.3 多路分支switch-case

switch(type){
    case 1:
    printf("你好!");
    break;
    case 2:
    printf("早上好!");
    break;
    case 3:
    printf("晚上好!");
    break;
    case 4:
    printf("再见!");
    break;
    default:
    printf:("类型输入错误!")
}

         控制表达式(type位置处)可以是变量也可以是表达式,类型必须为int。常量可以是常数也可以是常数计算的表达式(这个表达式的最终结果为常数)。

//级联的if-else
//成绩的分段划分
int gradeDivide(){
    int grade;
    printf("请输入你的成绩(0-100):\n");
    scanf("%d",&grade);
    if(grade>=0&grade<=100){
        grade /=10;
        switch(grade){
        case 10:
        case 9:
        printf("您的成绩评级为A\n");
        break;
        case 8:
        printf("您的成绩评级为B\n");
        break;
        case 7:
        printf("您的成绩评级为C\n");
        break;
        case 6:
        printf("您的成绩评级为D\n");
        break;
        default:
        printf("您的成绩评级为F\n");
        break;
        }
    }else{
    printf("您输入的成绩有误!")
    }
}

4.4 循环案例

4.4.1 猜数

●随机数:通过调用rand()函数来创建一个随机整数,

        现在对

        srand(time(0));
        int number = rand()%100+1;

        这两句话不要求理解

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

●x%100的结果是[0,99]中的一个整数

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

int guessNumber() {
	srand(time(0));/*设定随机数的产生与系统时钟关联*/
	int number = rand()%100+1;//存放产生的随机数
	int count = 0;//猜的次数
	int guessNumber = 0;//用户猜的数
	do {
		printf("请猜一下这个0-100的随机数是多少:\n");
		scanf("%d", &guessNumber);
		count++;
		if (guessNumber > number) {
			printf("您猜的这个数过大!");
		}
		else if (guessNumber < number) {
			printf("您猜的这个数过小!");
		}
	} while (number != guessNumber);
	printf("恭喜您猜对了!您一共猜了%d次",count);

}

4.4.2 数字倒置 

//数字倒置
int positionInversion() {

	int x = 0;
	printf("请输入要倒置的数:");
	scanf("%d", &x);
	int temp = x;
	int digit;
	int ret = 0;

	while (x>0) {
		digit = x % 10;
		ret = ret * 10 + digit;
		x = x / 10;
	}
	printf("%d的倒置结果为%d", temp, ret);
}

4.5 判断和循环的常见错误

4.5.1 忘了大括号

int main(){

int age = 0;
double salary = 4000;

if(age>60)
    salary = salary*1.2;
    printf("%d",salary);

}   
 
/*        age = 65
    代码执行:
          4800.0000
         [finish in 0.1s]
*/
int main(){

if(age>60)
    salary = salary*1.2;
    printf("%d",salary);

}  
/*        age = 55
    代码执行:
          4800.0000
          [finish in 0.1s]
*/

int main(){

if(age>60){
    salary = salary*1.2;
    printf("%d",salary);
    }
}  

/*        age = 55
    代码执行:
          [finish in 0.1s]
*/

4.5.2 if后面的分号

        if条件判断完之后,它会继续执行一条语句-分号之前,但是分号之前没有语句,因此它什么都没做,下面大括号就成了一条新的代码,无论条件成不成立下面大括号里面的代码块都会执行。

if(age>60)
    ;
{   salary = salary*1.2;
    printf("%d",salary);
} 

4.5.3 错误使用==和=

int main(){
    int age = 0;
    double salary = 4000;
    age = 55;
    if(age = 0){
        salary = salary*1.2;
        printf("%d %f\n",age,salary);
    }
}
/*
    将0赋给了age,条件判断的值为0时等于等式不成立代码块中的值无法执行
    if()    if只要求其中的值为0或1,为零代码块中的语句不执行。
*/

4.5.4 困惑的else

●if和else之后必须用上大括号形成语句块

●大括号的语句缩进一个tab的位置

4.5.5 三种代码风格

//第一种

if(x<0){
    f = -1;
}else if( x==0){
    f = 0;
}else{
    f = -2;
}

//第二种
if(x<0)
{
    f = -1;
}else if(x==0)
{   
    f = 0;
}else
{
    f = 2 * x ;
}

//第三种
if(x<0)
{
    f = -1;
}
else if(x==0)
{
    f = 0;
}
else
{
    f = 2 * x; 
}

 5 循环控制

5.1 循环控制

break与continue

break:跳出循环

continue:跳出当次循环

5.1.1 素数判断

int searchPrime() {
	int x;
	printf("请输入将要测试的数:\n");
	scanf("%d",&x);
	int i;/*除数*/
	int isPrime = 1;/*是素数*/
	for (i = 2; i < x ; i++) {
		if (x%i==0) {
			isPrime = 0;
			break;
		}
	}
	if (isPrime == 1) {
		printf("%d是素数",x);
	}
	else {
		printf("%d不是素数",x);
	}
}

5.2 多重循环

5.2.1 嵌套的循环

5.2.1.1 100以内的素数 
int searchPrime100() {
	int x;
	for (x = 2; x < 100;x++) {
		int i;
		int isPrime = 1;
		for (i = 2 ; i < x;i++) {
			if (x%i==0) {
				isPrime = 0;
				break;
			}
		}
		if (isPrime==1) {
			printf("%d ", x);
		}
	}
}
5.2.1.2  前50的素数
int searchPrime50() {
	int x = 2;
	int cnt = 0;/*counter计数器*/
	while ( cnt < 50) {
		int i;
		int isPrime = 1;
		for (i = 2; i < x; i++) {
			if (x % i == 0) {
				isPrime = 0;
				break;
			}
		}
		if (isPrime == 1) {
			printf("%d\n ", x);
			cnt++;
		}
		x++;
	}
	printf("cnt=%d", cnt);
}

5.2.2 从嵌套的循环中跳出 

5.2.2.1 凑硬币
int gatherCoins() {
	int x;
	int one, two, five;
	//scanf("%d",&x);
	x = 2;
	for (one = 1; one < 10 * x;one++) {
		for (two = 1; two < 10 / 2 * x;two++) {
			for (five = 1; five < 10 / 5 * x;five++) {
				if (one*1+two*2+five*5==10*x) {
					printf("%d个一角和%d个两角和%d个5角可以凑成%d元\n",
						one, two, five, x);
					
				}
			}
		}
	}
}

break和continue只能跳出当次循环 

 5.2.2.2 接力break
int gatherCoinsRelayBreak() {
	int x;
	int one, two, five;
	//scanf("%d",&x);
	int exit = 0;
	x = 2;
	for (one = 1; one < 10 * x; one++) {
		for (two = 1; two < 10 / 2 * x; two++) {
			for (five = 1; five < 10 / 5 * x; five++) {
				if (one * 1 + two * 2 + five * 5 == 10 * x) {
					printf("%d个一角和%d个两角和%d个5角可以凑成%d元\n",
						one, two, five, x);
					exit = 1;
					break;
				}
			}
			if (exit == 1) break;
		}
		if (exit == 1) break;
	}
}
5.2.2.3 goto 

goto关键字是指当条件满足后跳转到标号(out:)所在的位置

goto的缺点:可能导致代码出现不易理解得跳转,破坏了代码得结构,使得代码难以调试和维护。 

int gatherCoinsGoto() {
	int x;
	int one, two, five;
	//scanf("%d",&x);
	x = 2;
	for (one = 1; one < 10 * x; one++) {
		for (two = 1; two < 10 / 2 * x; two++) {
			for (five = 1; five < 10 / 5 * x; five++) {
				if (one * 1 + two * 2 + five * 5 == 10 * x) {
					printf("%d个一角和%d个两角和%d个5角可以凑成%d元\n",
						one, two, five, x);
					goto out;
				}
			}
		}
	}
out:
	;
}

5.3 循环应用

5.3.1 前n项和

5.3.1.1 前n项和
int sumOfFirstNTerms() {
	int n;
	int i;
	double sum = 0.0;
	scanf("%d",&n);
	for (i = 1; i < n;i++) {
		sum += 1.0 / i;
	}
	printf("前%d项和的结果是%f\n", n, sum);
}
5.3.1.2 前n项差和
int sumOfFirstNTerms() {
	int n;
	int i;
	double sum = 0.0;
	int sigin = 1;
	scanf("%d",&n);
	for (i = 1; i <=n;i++) {
		sum += sigin*1.0 / i;
		sigin = -sigin;
	}
	printf("前%d项和的结果是%f\n", n, sum);
}

5.3.2 求最大公约数(greatest common divisor)

5.3.2.1 辗转相除法求最大公约数
int divisionAlgorithm() {
	/*如果b等于0,计算结束,a就是最大公约数;
	否则,计算a除以b的余数,让a等于b,而b等于那个余数;
	回到第一步。
	a   b   remainder(余数)
	12	18	12
	18	12	6
	12	6	0
	6	0
	*/
	int a, b;
	int r;
	scanf("%d %d", &a, &b);
	printf("%d %d\n", a, b);
	while (b != 0) {
		r = a % b;
		a = b;
		b = r;
		printf("a=%d,b=%d,r=%d\n", a, b, r);
	}
	printf("gcd = %d\n", a);
}

5.3.3 整数分解(integer factorization)

5.3.3.1 逆序整数分解(inverse integer decomposition)
int inverseIntegerDecomposition() {
	int x;
	scanf("%d",&x);
	do {
		int d = x % 10;
		printf("%d", d);
		if (x > 9) {
			printf(" ");
		}
		x = x / 10;
	} while (x>0);
}

/*
    输入:12345
    输出:5 4 3 2 1
*/
5.3.3.2 正序整数分解(positive sequence integer decomposition)
5.3.3.2.1 固定位数
int positiveSequenceIntegerDecompositionFixedDigit() {
	/*
	int x = 12345;
	scanf("%d",&x);
	x = 12345;
	12345/10000 ->1;
	12345%10000 ->2345;
	10000/10 ->1000;
	2345/1000 ->2;
	2345%1000 ->345;
	1000/10 ->100;
	345/100 ->3;
	345%100 ->45;
	100/10 ->10;
	45/10 ->4;
	45%10 ->5;
	10/10 ->1;
	5/1 ->5;
	5%1 ->0;
	*/

	int x = 70000;
	int mask = 10000;
	do {
		int d = x / mask;
		printf("%d", d);
		if (mask>9) {
			printf(" ");
		}
		 x %= mask;
		//printf("x = %d\n ", x);
		mask /= 10;
	} while (mask>0);
}
5.3.3.2.1 未固定位数

函数        pow(a,b)        a的b次方        a^b

int positiveSequenceIntegerDecompositionUnFixedDigit() {
	int x;
	scanf("%d",&x);
	int t = x;
	int mask = 1;
	int cnt = 0;
	while (x > 9) {
		x /= 10;
		mask *= 10;
		cnt++;
	}
	//printf("%d %d", mask, cnt);
	x = t;
	do {
		int d = x / mask;
		printf("%d", d);
		if (mask > 9) {
			printf(" ");
		}
		x %= mask;
		//printf("x = %d\n ", x);
		mask /= 10;
	} while (mask > 0);
}

/*
    输入:1234567
    输出:1 2 3 4 5 6 7
*/

 6 数组与函数

6.1 数组

6.1.1 认识数组

6.1.1.1 数组的输入
int number[100];/*数组容量*/
scanf("%d",&number[i]);

QUESTION: while循环中为什么结束循环的条件是-1         已理解

int cognitiveArray() {
	int x;
	int cnt = 0;
	double sum = 0;
	int number[100];
	scanf("%d", &x);
	while (x!=-1) {
		number[cnt] = x;

		printf("%d\n",number[cnt]);

		cnt++;
		sum += x; 
		scanf("%d", &x);
	}
	if (cnt>0) {
		double average = sum / cnt;
		printf("%f\n", average);
		int i;
		for (i = 0;i<cnt;i++) {
			if (number[i] > average) {
				printf("%d\n",number[i]);
			}
		}
	}
}
/*
1 2 3 4 5
1
2
3
4
5
-1
3.000000
4
5
*/

6.1.2 定义数组

6.1.2.1 数组的格式

<类型> 变量名[元素类型];

 6.1.2.2 数组的特点

● 其中所有的元素具有相同的特点

●一旦创建,不能改变大小

●数组中的元素在内存中是连续依次排列的(重点)

  6.1.2.3 int a[10]

●a[2] = a[1]+6;

●将a[1]的值加6赋给a[2]

●在赋值号左边的叫做左值 

 6.1.2.4 数组的单元

●数组的单元就是数组的一个变量

●使用数组时放在[]中的数字叫作下标或者索引,下标从0开始计数

  6.1.2.5 有效的下标范围

●编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读或者写

●一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃

        *segmentation fault

●但是也可能运气好,没有造成严重后果

●程序员的责任:保证程序只使用有效的下标[0,数组范围-1]

6.1.2.6 字符可以作为下标吗? 

int a[225];

a[A] = 1;

在大多数编程语言中,数组的下标必须是整数类型。

字符可以被用作数组的下标,但实际上它们会被转换为相应的整数值。

在C语言中,字符类型被表示为ASCI码,所以字符'A'的ASCII码值为65。因此,a[A]实际上等同于a[65],这样就可以将值赋给数组a的第66个元素。然而,需要注意的是,在使用字符作为数组下标时,要确保字符的取值范围在数组大小范围内,否则可能会导致访问越界。在上面的例子中,数组a的大小是225,因此只有在字符的ASC码值在0到254之间时才是安全的。如果字符的ASC码值超过了这个范围,就可能导致数组越界访问,导致不可预测的行为和错误。

6.1.3 用数组作散列计算

int arrayTest() {
    //设定数组的大小
	const int number = 10;
	int x;
    //定义数组
	int count[10];
	int i;
    //对数组进行初始化
	for (i = 0; i < number;i++) {
		count[i] = 0;
	}
	scanf("%d", &x);
	while (x!=-1) {
		if (x>=0&&x<=9) {
            //数组参与计算
			count[x]++;
		}
		scanf("%d", &x);
	}
    //遍历数组输出
	for (i = 0; i < number;i++) {
		printf("%d:%d次\n", i, count[i]);
	}
}
1 1 2 3 3 3 4 4 4 5 5 6 6 6 6 7 7 7 7 7 8 9 9 9 -1
0:0次
1:2次
2:1次
3:3次
4:3次
5:2次
6:4次
7:5次
8:1次
9:3次

 BUG:在c语言编程中这样定义数组时编译器回报一个错误  已解决

	const int number = 10;
	int x;
	int count[number];

question:C99之后都可以用变量定义数组的大小,为什么用vs2022写会报错?

原因:

● 在C99标准中,确实支持使用变量来定义数组的大小,这被称为变长数组(Variable Length Arrays, VLA)。然而,并非所有的C编译器都完全支持C99的所有特性,特别是变长数组。Visual Studio(包括VS2022)的C编译器默认使用的是Microsoft的C编译器,它主要遵循C89/C90标准,并不完全支持C99中的变长数组特性。

6.2 函数的定义与使用

6.2.1 初识函数

函数的优点:

●降低重复代码的书写

●便于后期维护

6.2.2 函数的定义与使用

●函数的定义:

函数是一块代码,用来接零个或者多个参数,做一件事,并返回零个或者多个值

函数的组成:

●函数是由函数头和函数体组成,函数头又由返回值类型、函数名以及参数组成,参数之间用逗号隔开

函数的调用:

格式:函数名(参数值);

注意:

●()表示了函数调用的作用        

●没有参数也需要()

●如果有参数则要按照要求输入参数 

6.2.3 从函数中返回

return  停止函数的执行,并返回一个函数值

return;

return 表达式;

理念:单一出口,便于维护

返回的值:

        ●可以赋值给变量

        ●可以再传给函数

        ●也可以丢掉

int functionInvocation() {
	int a, b;
	a = 3;
	b = 4;
	int max =  maxValue(a,b);
	printf("%d", max);
}
int maxValue(int a, int b) 
{
	int max = 0;
	if (a > b) {
		max = a;
	}
	else {
		max = b;
	}
	return max;
}
/*
    运行结果:    
              4
*/

BUG:max不能作为函数名

6.3 函数的参数与变量

6.3.1 函数原型

void sum(int begin,int end);    //原型声明    参数名(begin)可以不写

int main()
{
    sum(1,10);

    return 0;
}

void sum(int begin,int end)            /*函数的定义*/
{
    int i;
    int sum = 0;
    for(i=begin;i<end;i++){
    sum +=i;
    }
    printf("%d到%d的和是%d\n",begin,end,sum);
}

函数的原型

函数头:以分号“;”结尾,就构成了函数的原型

C语言编译器是自上而下的分析代码

函数原型的目的是告诉编译器这个函数长什么样,并检验函数的调用是否正确

·名称

·参数(数量及类型)

·返回类型 

声明的参数里面可以不写参数的名字,只写类型就可以了,写名字只是为了方便程序员的理解

6.3.2 参数传递

如果函数有参数,调用函数时必须传递给它数量、类型正确的值

●可以传递给函数的值是表达式的结果(参数),这包括:

●字面量

●变量

●函数的返回值

●计算的结果

默认自动类型转换

调用函数给的值和参数类型不匹配是C语言传统上最大的漏洞//?

编译器总是悄悄地把类型转换好,但结果不一定是期望的

值传递

●C语言在调用函数地时候永远只能传递值给函数

●每个函数都有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系

●函数关系表(或者函数声明)中的参数叫作形式参数(形参format parameter),调用函数时给的值叫做实际参数(实参)

void swap(int a,int b);    /*参数*/

int main(){
    int a = 5;
    int b = 6;
    swap(a,b);            /*值*/
    printf("a=%d b = %d\n", a , b );
    return 0;
}

void swap(int a,int b){    /*参数*/
    int t = a;
    int a = b;
    int b = t;
}

6.3.3 本地变量

本地变量

函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量

定义在函数内部的变量就是本地变量(目前所认识到的变量都是本地变量)

参数也是本地变量

变量的作用域和生存期

生存期:

变量从开始出现到消亡

作用域:

在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)

本地变量的规则

本地变量是定义在块内的

        它可以是定义在函数的块内

        也可以定义在语句的块内

        甚至可以随便拉个大括号来定义变量

程序在进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了

块外面定义的变量在里面仍然有效

块里面定义的变量和外面的变量名重复了,块中的变量会覆盖掉外面变量在块中的值

块中变量名不能重复

本地变量不会被初始化(参数在传入的时候会被默认初始化)

6.3.4 杂事

6.3.4.1 空参

函数声明的时候要声明参数的类型,有参数就填有参数,没参数就填void,不能空着

6.3.4.2 逗号和逗号运算符的区分

f(a,b)            逗号

f((a,b))          逗号运算符

6.3.4.3 C语言的函数不允许有嵌套定义

函数体中还可以有函数声明但是不能有函数定义(body)

6.3.4.4

int i,j,sum(int a,int b);这是定义了两个变量i和j,声明了一个叫sum的函数,这个函数要两个int做参数,并且返回值为int
不建议这么写程序,但C能接受 

 6.3.4.5 关于main()

第一种写法:int main()也是一个函数                //常见的标准写法

第二种写法:int main(void)也没错                    //明确告诉main不能传递参数

第三种写法:int main(int argc,char* argv[])      //写上参数也可以不使用,只有在实现一些命令行的功能的时候才会使用,初学不建议,后期可以探索

两个参数介绍的链接:主函数 - cppreference.com

return 0 也是有意义的,在程序运行结束后,return会将0返回给调用它的地方,那个程序会检查返回的值并将这个结果报告给系统

  6.3.4.5 源码、补码、反码

由:计算机中只有加法器,计算减法的时候需要将减法转换成加法

源码:

数的二进制表达形式

首位为标志位,0为正数,1为负数,下为14和-12的二进制表示形式

00001110
10010101

 反码:

正数的源码、反码和补码相同,为源码本身

负数原码转反码:符号位不变其他位相反

-21的反码

11101010

补码:

在原来反码的基础上+1

11101010
1
11101011

4、计算机内计算方式
    14+(-21) = -7;
    补码+补码
    0 0 0 0 1 1 1 0
 +  1 1 1 0 1 0 1 1
  -------------------
    1 1 1 1 1 0 0 1         -7的补码
 -                1
  -------------------  
    1 1 1 1 1 0 0 0         -7的反码
  -------------------
    1 0 0 0 0 1 1 1         -7的源码

6.4 二维数组

6.4.1 二维数组的定义

int a[3][5];

通常理解a为3行5列的矩阵

a[0][0]a[0][1]a[0][2]a[0][3]a[0][4]
a[1][0]a[1][1]a[1][2]a[1][3]a[1][4]
a[2][0]a[2][1]a[2][2]a[2][3]a[2][4]

 6.4.2 二维数组的遍历

for(i=0;i<3;i++){
    for(j=0;j<5;j++){
    a[i][j] = i*j;
    }
}

a[i][j]是一个int

表示第i行第j列上的单元

       a[i,j]是什么

6.4.3 二维数组的初始化 

int a[][5] = {
    {1,2,3,4,5},
    {6,7,7,8,8},
};
  • 列数必须给出,行数可以由编译器来数;
  • 每行一个大括号{},用逗号分隔
  • 最后的逗号可以存在,有古老的传统
  • 如果省略,可以补零 

 用C语言中const常量定义数组大小会报错

 常量:在程序运行期间,从开始到结束都不会改变的值,称为常量。未分配空间

 只读变量:在被const修饰的变量叫做只读变量或者常变量。

 C语言规定数组定义时下标 必须是 常量只读变量 是不可以的

const int size = 10;
int a[size];

//C程序报错
//error:variably modified 'a' at file scope

 6.4.4 棋盘游戏

void twoDimendionalArray() {
	//int size = 3;
	int board[3][3];
	const int   size = 3;
	//int board[size][size];
	int numOfX;
	int numOfO;
	int i, j;
	int result = -1;	//-1:没人赢		0:o赢了	1:x赢了
	//存入数据
	for (i = 0 ; i < size; i++) {
		for (j = 0; j < 3;j++) {
			scanf("%d", &board[i][j]);
		}
	}

	//输出数据
	int m, n;
	for (m = 0; m < size; m++) {
		for (n = 0; n < 3; n++) {
			printf("%d ", board[m][n]);
		}
		printf("\n");
	}

	//检查行
	for (i = 0; i < size && result == -1;i++) {
		numOfO = numOfX = 0;
		for (j = 0; j < 3;j++) {
			if (board[i][j]==0) {
				numOfO++;
			}
			else {
				numOfX++;
			}
		}
		if (numOfO == 3) {
			result == 0;
		}
		else if (numOfX == 3) {
			result == 1;
		}
	}

	//检查列(为什么这里要有一个外部判断)
	if (result==1) {
		for (j = 0; j < size && result == -1; j++) {
			numOfO = numOfX = 0;
			for (i = 0; i < size;i++) {
				if (board[i][j] == 0) {
					numOfO++;
				}
				else if (board[i][j] == 1) {
					numOfX++;
				}
			}
		}
		if (numOfO== size) {
			result = 0;
		}
		else if (numOfX == size) {
			result = 1;
		}

	}

	//检查左对角线
	numOfO = numOfX = 0;
	for (i = 0; i < size && result == -1;i++) {
		if (board[i][i]==0) {
			numOfO++;
		}
		else if (board[i][i] == 1) {
			numOfX++;
		}
	}
	if (numOfO == size) {
		result = 0;
	}
	else if (numOfX == size) {
		result = 1;
	}

	//检查右对角线
	numOfO = numOfX = 0;
	for (i = 0; i < size;i++) {
		if (board[i][size - i - 1] == 0) {
			numOfO++;
		}
		else if (board[i][size - i - 1] == 1) {
			numOfX++;
		}
	}
	if (numOfO == size) {
		result = 0;
	}
	else if (numOfX == size) {
		result = 1;
	}
	printf("result=%d", result);
}
0 1 0 0 0 1 1 0 1
0 1 0
0 0 1
1 0 1
result=-1
1 0 0 0 1 1 1 0 1
1 0 0
0 1 1
1 0 1
result=1

7 数组运算

7.1 数组运算

7.1.1 数组运算

7.1.1.1 数组的集成初始化

定义数组变量的两种形式:

●int arr[10];        //只设置数组的大小,不对数组进行具体的赋值

●int arr[]={1,2,3,4,5,6,7};        //对数组进行初始化赋值,这种方式叫作数组的集成初始化

初始化的另几种方式:

● 当赋值数量不足数组容量时,编译器会自动在后方补零

    int arr[10] = {2,3};

    打印结果:

     2 3 0 0 0 0 0 0 0 0 

●  因此,也可以这样写

    int arr[10] = {0};

    打印结果:

     0 0 0 0 0 0 0 0 0 0 

●  给特定位置赋值(这种写法只适用C99)

    int a[13] = {

                [1] = 1,[3] = 1,1,[6] = 1

     };

    运行结果:

        0 1 0 1 1 0 1 0 0 0 0 0 0 0

● vs2022(微软C语言编译器适用的标准是C89/C90)只能这样写

    int a[10] = {0};
    a[8] = 6;
    a[5] = 3;
    运行结果:

    0       0       0       0       0       3       0       0       6       0

 7.1.1.2 sizeof

sizeof给出整个数据所占据的内容大小,单位是字节

●通过以下表达式可以得出数组的大小

        sizeof(a)/sizeof(a[0])

●一个数所占的内存为4个字节

 7.1.1.3 数组的赋值

数组本身不能直接赋值

int a[] = {2,4,5,7,7,5,312,3};
int b[] = a;/*报错语法*/
要把一个数组赋值给另一个数组只能通过遍历的方式
 for(I=0;i<=length;i++){
    b[i] = a[i];
 }

  ●用for循环遍历,让循环变量i从0到<数组的长度,这样最大的好处是循环变量i的最大值刚好等于数组的最大索引

数组作为参数传递给函数时:

 ●必须用另一个参数来确定数组的大小

 ●不能在[ ]中        给出数组的大小

 ●不能在用sizeof来计算数组的元素个数(指针会讲)

void searchElement() {
	int x;
	int a[] = {1,2,3,4,5,6,7,8,9,0};
	printf("请您输入您要找的数:\n");
	scanf("%d", &x);
	int length = sizeof(a) / sizeof(a[0]);
	int result = searchIndex(x,a,length);
	if (result==-1) {
		printf("此数组中并没有您所找的数!");
	}else{
		printf("您所找的的这个数的下标是%d", result);
	}
	
}
int searchIndex(int x, int arr[], int length) {
	int result = -1;
	int i;
	for (i = 0; i < length;i++) {
		if (arr[i] == x) {
			result = i;
		}		
	}
	return result;
}

 运行结果:

请您输入您要找的数:
5
您所找的的这个数的下标是4
请您输入您要找的数:
23
此数组中并没有您所找的数!

 7.1.2 找素数

7.1.2.1 方案1-逐一测试
int primeJudgeMentCase1(int x) {
		/*从2开始挨个试*/
		int ret = 1;
		if (x == 1) ret = 0;
		int i;
		for (i = 2; i < x; i++) {
			if (x % i == 0) {
				ret = 0;
				break;
			}
			else {
				ret = 1;
			
			}
		}
		return ret;
	}
7.1.2.2 方案2-先把偶数去掉
int primeJudgeMentCase2(int x) {
	int ret = 1;
	if (x == 1 || (x % 2 == 0 && x == 2)) ret = 0;
	int i;
	for (i = 3; i < x; i += 2) {
		if (x % i == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}
7.1.2.3 方案3-只走到平方根
int primeJudgeMentCase3(int x) {
	/*只走到平方根*/
	int ret = 1;
	if (x == 1 || (x % 2 == 0 && x == 2)) ret = 0;
	int i;
	for (i = 3; i < sqrt(x); i += 2) {
		if (x % i == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}

●判断一个数是否为素数时,我们只需要检查到该数的平方根即可,原因如下

        首先,我们需要明确什么是素数。一个素数是指只有1和它本身两个正因数的自然数。因此,要判断一个数n是否为素数,我们需要检查从2到n-1的所有数是否能整除n。

        然而,我们实际上并不需要检查到n-1。假设n有一个大于它的平方根sqrt(n)的因数a,那么它必然还有一个小于sqrt(n)的因数b,使得n = a * b。这是因为如果a和b都大于sqrt(n),那么它们的乘积会大于n,这与我们的假设矛盾。因此,如果n有一个大于sqrt(n)的因数,我们必然能在小于sqrt(n)的范围内找到它的另一个因数。

7.1.2.4 方案4-只试素数因子
int isPrime(int isX,int arr[],int numberOfKnowPrimes) {
	int ret = 1;
	int i;
	for (i = 0; i < numberOfKnowPrimes; i++) {
		if (isX % arr[i] == 0)
		{
			ret = 0;
			break;
		}
	}
	return ret;
}

int main() {
	int arr[10] = { 2 };
	int x = 3;/*要判断的数*/
	int count = 1;/*数组的下标*/
	while (count<10) {
	if (isPrime(x, arr, count)) {
		arr[count++] = x;
		}
	x++;
	}

	{
		/*要遍历的数组*/
		int i;
		for (i = 0; i < 10; i++) {
			printf("%d ", arr[i]);

		}
	}
}
7.1.2.5 方案5-判断是否能被已知的且小于x的素数整除 
int main() {
	int prime[100] = { 2 };
	int cnt = 1;//用来统计素数表中的素数
	int i = 3;//2已经给出了,从三开始判断
	while(cnt<100){
		if (isPrime(i, prime, cnt/*数组中已有的元素*/)) {
			prime[cnt++] = i;
		}
		i++;
	}
	for (int i = 0; i < 100;i++) {
		printf("%d", prime[i]);
		if ((i + 1) % 5 )  printf("\t"); 
		else { printf("\n"); }
	}
	return 0;

}

int isPrime(int x, int knowOfprime[], int len) {
	int ret = 1;
	int i;
	for (i = 0; i < len;i++) {
		if (x % knowOfprime[i] == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}

对if条件判断掌握不足 

C语言中的if语句用于基于某个条件执行一段代码。当该条件为真(非零)时,if语句后的代码块会被执行。如果条件为假(零),则跳过该代码块。

运行结果:
2       3       5       7       11
13      17      19      23      29
31      37      41      43      47
53      59      61      67      71
73      79      83      89      97
101     103     107     109     113
127     131     137     139     149
151     157     163     167     173
179     181     191     193     197
199     211     223     227     229
233     239     241     251     257
263     269     271     277     281
283     293     307     311     313
317     331     337     347     349
353     359     367     373     379
383     389     397     401     409
419     421     431     433     439
443     449     457     461     463
467     479     487     491     499
503     509     521     523     541
7.1.2.6 方案6-构造素数表 -埃拉托斯特尼筛法(Sieve of Eratosthenes) 

●令x为2

●将2x、3x、4x直至ax<n的数标记为非素数        

●令x为下一个没有标记为非素数的数,重复2;直至所有的数都已尝试完毕

伪代码:

●欲构造n以内(不含)的素数表

1.开辟prime[n],初始化其所有元素为1,prime[x]为1表示x是素数

2.令x = 2

3.如果x是素数,则对于(i=2;x*i<n;i++)令prime[i*x] = 0

4.令x++,如果x<n,重复3,否则结束

int main() {
	int prime[10];
	int arrSize = (sizeof(prime)/sizeof(prime[0]));
	int i;
	for (i = 0; i < arrSize;i++) {
		prime[i] = 1;
	}
	int x = 2;
	for (x = 2; x < arrSize;x++) {
		if (prime[x]) {
			for (i = 2; i *x< arrSize;i++) {
				prime[i * x] = 0;
			}
		}
	}

	{
		/*要遍历的数组*/
		int i;
		for (i = 2; i < arrSize; i++) {
			if(prime[i]==1)printf("%d ", i);

		}
	}
}

创建素数表的算法总结:

●埃拉托斯特尼筛法怎么保证下一个未被标记的数是素数:

埃拉托斯特尼筛法(Sieve of Eratosthenes)通过一系列步骤来确保下一个未被标记的数是素数。这一算法的核心思想是,任何合数必然有一个小于等于它平方根的素数因子。因此,当我们在筛法过程中遍历每一个数,并标记其素数倍数为合数时,任何合数都会被其小于等于它平方根的素数因子所标记。

具体来说,埃拉托斯特尼筛法的过程如下:

  1. 从最小的素数2开始,将所有2的倍数(除了2本身)标记为合数。
  2. 然后找到下一个未被标记的数,这个数必然是素数(因为所有它的小于它的因数都已经被标记为合数了)。
  3. 接着,用这个新找到的素数去标记它的所有倍数为合数。
  4. 重复这个过程,直到遍历完所有需要检查的数。

在这个过程中,由于我们总是用当前找到的最小素数去标记其倍数,这就保证了任何合数都会被它的小于等于它平方根的素数因子所标记。因此,当一个数在遍历过程中未被标记,那么它必然是素数,因为它没有任何小于等于它平方根的素数因子。

这就是埃拉托斯特尼筛法能够保证下一个未被标记的数是素数的原因。

拉托斯特尼筛法的优化算法:

●线性筛法(线性时间筛法):

●Wheel筛法:

7.2 搜索

7.2.1 线性搜索

线性搜索:就是对数组进行线性遍历

int search(int x,int arr[],int length) {
	int i;
	int ret = -1;
	for (i = 0; i < length;i++) {
		if (x==arr[i]) {
			ret = i;
            break;
		}
	}
	return ret;
}

int main() {
	int arr[] = {3,45,34,23,25,10,21,22,50,20};
	int x;
	printf("请输入您想查找的数:\n");
	scanf("%d",&x);
	int ret = search(x,arr,sizeof(arr) / sizeof(arr[0]));
	if (ret==-1) {
		printf("此数组中没有您要查找的数!");
	}
	else {
		printf("您查找的数的索引为%d", ret);
	}
}

7.2.2 搜索的例子

BUG:在VS2022中使用opencv编写C++程序时出现如下错误

const char*"类型的值不能用于初始化"char*"类型的实体

原因:可能的原因是在VS2017版本中使用这种char*的表达方式会造成程序崩溃,所以VS2017对其进行了控件管理。

解决方案:

1、右键project -> 属性 -> C/C++ -> 语言 -> 符合模式:否

2、在前面加上const

3、对变量进行强转

        char* image_window =(char*) "Source Image";
        char* result_window = (char*)"Result window";
int amount[] = {1,5,10,25,50};
char *name[] = {"penny","nickel","dime","quarter","half-dollar"};
struct {
	int amount;
	char* name;
}coins[] = {
	{1,"penny"},
	{5,"nickel"},
	{10,"dime"},
	{25,"quarter"},
	{50,"half-dollar"}
};
//int search(int key,int a[],int len) {
//	int ret = -1;
//	for (int i = 0; i < len;i++) 
//	{
//		if (key==a[i]) 
//		{
//			ret = i;
//			break;
//		}
//	}
//	return ret;
//}

int main() {
	int k = 10;
	//int r = search(k,amount,sizeof(name)/sizeof(amount[0]));
	for (int i = 0; i < sizeof(coins) / sizeof(coins[0]);i++) 
	{
		if (k==coins[i].amount) {
			printf("%s\n", coins[i].name);
			break;
		}
	}
}

7.2.3 二分搜索

7.2.3.1 二分搜索的优点
Nlog2N
103
1007
100010
1,000,00020
1,000,000,00030
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>  
int arr[] = { 2,3,5,6,7,11,15,25,27,34,56 };
int search(int key,int arr[],int len) {
	int light = 0;
	int right = len - 1;
	int ret = -1;
	while (light<right) {
		int mid = (light + right) / 2;
		if (key == arr[mid]) {
			ret = mid;
			break;
		}
		else if
			(key > arr[mid]) {
			light = mid + 1;
		}
		else if
			(key < arr[mid]) {
			right = mid - 1;
		}
	}
	return ret;
}
int main() {

	int len = sizeof(arr) / sizeof(arr[0]);
	int key = 12;
	int ret = search(key,arr,len);
	if (ret == -1) {
		printf("该数组中没有这一项数据!");
	}
	else {
		printf("所要寻找的数据的索引在%d", ret);
	}
}
所要寻找的数据的索引在5
D:\C#data\test\x64\Debug\test.exe (进程 26020)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

7.3 排序初步-选择排序

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>


int max(int arr[],int length) {
	int maxId = 0;
	for (int i = 1; i < length;i++) {
		if (arr[i]>arr[maxId]) {
			maxId = i;
		}
	}
	return maxId;
}


int main() {
	int arr[] = {{9,8,7,6,5,4,3,2,1,0} };
	int maxId = 0;
	int len = sizeof(arr) / sizeof(arr[0]);
    //选择排序
	for (int i = len - 1;i>0 ;i--) {
		maxId = max(arr, i+1);
		int temp = arr[maxId];
		arr[maxId] = arr[i];
		arr[i] = temp;

	}
	for (int i = 0; i < len;i++) {
		printf("%d ", arr[i]);
	}
	return 0;
}
0 1 2 3 4 5 6 7 8 9
D:\C#data\test\x64\Debug\test.exe (进程 22848)已退出,代码为 0。

8 指针与字符串

8.1 指针

8.1.1 取变量地址的运算符:&

8.1.1.1 sizeof

sizeof是一个运算符,用来计算出某一个变量或者类型的所占的字节数

int main() {
	printf("sizeof(int) = %d\n", sizeof(int));
	printf("sizeof(double) = %d\n", sizeof(double));
	int i;
	printf("sizeof(i) = %d", sizeof(i));
	return 0;
}
 8.1.1.2 &的作用

 &是一个运算符,它的作用:获取变量的地址,&的操作数必须是变量

int main() {
	int i;
	printf("p(i) = %p", &i);
	return 0;
}
p(i) = 000000B07ED6F9D4

● 获取变量的地址,操作对象必须是变量

● 地址的大小是否与int的值相等取决于编译器是32架构的还是64架构的

tips:C语言的内存模型 后面会学到

/*相邻变量的地址*/
int main() {
	int i;
	int j;
	printf("p(i) = %p\n", &i);
	printf("p(j) = %p", &j);
	return 0;
}
p(i) = 0000003F45F8FDB4
p(j) = 0000003F45F8FDD4
/*这两个变量的值差十六进制的20,十进制的32*/
D:\C#data\test\x64\Debug\test.exe (进程 3768)已退出,代码为 0。
/*数组的地址*/
int main() {
	int a[10];
	printf("%p\n", &a);
	printf("%p\n", a);
	printf("%p\n", &a[0]);
	printf("%p\n", &a[1]);
	return 0;
}
000000445537FA58
000000445537FA58
000000445537FA58
000000445537FA5C
/*a[0]和a[1]差4*/

 8.1.2 指针 翁恺的经典语录

8.1.2.1 指针

一个指针类型的变量就是保存地址的变量

int i;

int *p = &i;         //将变量i的地址存给p;

int* p,q;

int *p,q;      //*靠近p和*靠近int没有任何区别,但是*只声明了p为指针,q依然是int类型的变量

8.1.2.2 指针变量

● 变量的值是内存的地址

        ●普通变量的值是实际的值

        ●指针变量的值是具有实际值的变量的地址

//声明f()函数
/*
f()函数的作用:
	区分p和*p的值
	对变量重新赋值
*/
void f(int *p);

/*
	g()函数的作用:
	查看当前变量中具体的值
*/
void g(int k);

int main() {
	int i = 6;
	printf("&i = %p\n",&i);
	f(&i);
	g(i);
	return 0;
}


void f(int *p) {
	printf(" p = %p\n",p);
	printf(" *p = %d\n", *p);
	*p = 26;

}

void g(int k) {
	printf("k=%d", k);
}
&i = 000000308F53FA04
 p = 000000308F53FA04
 *p = 6
k=26

8.1.3 指针与数组

 8.1.3.1 数组与指针的关系
void minmax(int a[], int len, int* min, int* max);

int main() {
	int a[] = {1,2,3,4,5,6,6,7,8,9,10,11,22,33,44,55};
	int min,max;
	//查看数组a的大小
	printf("main sizeof(a) = %lu\n", sizeof(a));
	//查看main中数组a的地址
	printf("main  a = %p\n");
	//调用minmax函数
	minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);

	printf("min = %d\n", min);
	printf("max = %d\n", max);


	return 0;
}


void minmax(int a[],int len,int *min,int *max) {
	int i;
	//查看函数minmax数组a的大小
	printf("minmax sizeof(a) = %lu\n", sizeof(a));
	//查看函数minmax中数组a的地址
	printf("minmax a = %p\n");
	*min = *max = a[0];
	for (i = 1; i < len;i++) {
		if (a[i]<*min) {
			*min = a[i];
		}
		if (a[i]>*max) {
			*max = a[i];
		}
	}
}
main sizeof(a) = 64
main  a = 00007FF7AE93A8E0
minmax sizeof(a) = 8
minmax a = 00007FF7AE93A8E0
min = 1
max = 55

调用函数中的数组并不是数组本身而是数组的地址(指针)

四种等价的函数原型:

       ●int sum(int *ar,int n);

       ●int sum(int *,int);

       ●int sum(int ar[],int n);

       ●int sum(int [],int);  

●数组变量是特殊的指针,所以定义了一个数组后无需使用&,可以直接拿到地址

        int a[10];

        int *p = a;         

●数组的单元是变量,需要用&取地址

●数组的地址和数组中第一个元素的地址是一个地址

●数组变量是只读变量const,不能相互赋值        

/*错误表达*/
int a[] = {1,2,3};
int b[] = a;

正确打印指针的地址和指针的值

printf("*p = %d\n", *p);  // 打印p指向的值  
printf("p = %p\n", p);    // 打印p的地址

8.2 字符类型

8.2.1 字符类型

8.2.1.1 char

char是一种整数也是一种特殊的类型:字符。这是因为:

●用单引号表示的字符字面量:'a','1'

●''也是一个字符

●printf和scanf里用%c来输入输出字符

/*程序*/
	char c;
	scanf("%c", &c);
	printf("c=%d\n", c);
	printf("c=%c\n", c);
/*输入*/
    1
/*输出*/
    c=49
    c=1
	//程序
    int i; 
	char c;
	scanf("%d", &i);
	c = i;
	printf("c=%d\n", c);
	printf("c=%c\n", c);
//输入
    1
//输出
    c=1
    c= ''    //不是输出的空格,是程序无法输出
//输入
    49
//输出
    c=49
    c= '1'
 8.2.1.2 空格

空格字符对应的整数类型数字为32

/*带空格的程序*/
    int i; 
	char c;
	scanf("%d %c", &i,&c);
	printf("i=%d c=%d c=%c\n", i,c,c);
/*输入*/
12 1
/*运行结果*/
i=12 c=49 c=1

/*输入*/
12a
/*运行结果*/
i=12 c=97 c=a

/*输入*/
12  a    //两个空格
/*运行结果*/
i=12 c=97 c=a
/*不带空格的程序*/
    int i; 
	char c;
	scanf("%d%c", &i,&c);
	printf("i=%d c=%d c=%c\n", i,c,c);
/*输入*/
12 1
/*运行结果*/
i=12 c=32 c=

/*输入*/
12a
/*运行结果*/
i=12 c=97 c=a

/*输入*/
12  a    //两个空格
/*运行结果*/
i=12 c=32 c=

在%d后面没有空格,程序只读到整数结束为止,下一个由另一个字符接收;

在%d后面有空格,程序会读到空格完,再将下一个数交给另一个字符接收

8.2.2 逃逸字符

8.2.2.1 逃逸字符

用来表达无法印出来的控制字符或特殊字符,它由一个反斜杠''\''开头,再加上另一个字符,两个字符组合起来形成一个新的字符        

	printf("请输入\"5 7\"");
请输入"5 7"
D:\C#data\test\x64\Debug\test.exe (进程 20844)已退出,代码为 0。
常见逃逸字符
\b回退一格或者删除一格
\t到下一个表格位
\n换行
\r回车
\"双引号
\'单引号
\\反斜杠本身
\b
/*程序*/	
printf("123\b456");
/*输出*/
12456
\t    相当于按了一个tab键
/*程序*/	
printf("123\t456\n");
printf("12\t456");
/*输出*/    
123     456
12      456

 8.3 字符串

8.3.1 字符串

8.3.1.1 字符数组

定义一个字符数组

●char word[] = {'H','e','l','l','o','!'};

这是字符数组并不是字符串,不能用字符串的形式做运算

●char word[] = {'H','e','l','l','o','!','0'};

在字符数组最后加上一个0,使得这个字符数组也变成了一个字符串,它既是一个字符数组也可以用字符串的运算方式进行计算。

 8.3.1.2 字符串

字符串:以0(整数0)结尾的一串数字

●这里的0可以是整数也可以单引号反斜杠0(‘\0’),int一般为4个字节,而char为1个

●0标志着字符串的结束,但它不是字符串的一部分

●计算字符串的长度的时候不会把0包含进去

●字符串以数组的形式存在,以数组或者指针(更多)的形式访问

●string.h里有很多处理字符串的函数

#include<string.h>
  8.3.1.3 字符串变量的定义方式

●char *str = "hello";        ①用指针指向一个字符数组

●char word[] = "hello";    ②将这些字符放到大小相等的数组里(字符串字面量初始化字符数组)

●char line[10]  = "hello"; ③将这些字符放到一个容量大于字符数的数组中

这样定义完字符串后,编译器会在最后生成一个用来结尾的0

   8.3.1.4 字符串常量的定义方式

●"hello"

●"hello"会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的        0

●两个相邻的字符串常量会被自动连接起来

●可以用两对双引号来表示字符串常量让它们连接起来,也可以用一个反斜杠‘\’来拆分一对双引号使它成为两段字符串常量,这样做第二段必须顶格写不然打印的会有空格

8.3.1.5 字符串总结

●C语言的字符串是以字符数组的形态存在的

        ●不能用运算符对字符串做运算

        ●通过数组的方式可以遍历字符串

●唯一特殊的地方是字符串字面量可以用来初始化字符数组↑②

●标准库提供了一系列处理字符串的函数

8.3.2 字符串变量

8.3.2.1 字符串常(变?)量

int i;        本地变量

char *s = "hello  word!";

● s 是一个指针,初始化为指向一个字符串常量

        ● 由于这是个常量所在的地方,实际上s是const char* s,但是由于历史的原因,编译器接收不带const的写法

        ● 但如果试图对所指的字符串进行改写操作会导致严重的后果

● 如果需要修改字符串,应该用数组:char s[] = "hello  word!";

	int i = 0;
	char* s = "hello";
	printf("&i=%p\n",&i);
	printf("&s=%p\n",&s);
	printf("s=%p\n",s);
i =000000F43D55F7A4    //本地变量
&s=000000F43D55F7C8
s =00007FF75D569BB0
 8.3.2.2 字符串常量定义的选择

● char *s = "hello  word!";

● char word[] = "hello  word!";

①数组:这个字符串在这里,作为本地变量空间自动被回收

②指针:这个字符串不知道在哪里,

        ● 处理参数

        ● 动态分配空间

如果要构造一个字符串→数组

如果要处理一个字符串→指针

 8.3.2.3 char*不一定是字符串

 char*不是字符串

● 本意是指向字符的指针,可能是指向字符的数组(就想int*一样)

● 只有它所指的字符数组有结尾的0,才能说它所指的是字符串

 8.4 字符串的计算

8.4.1 字符串的输入输出

8.4.1.1 字符串的赋值

● char* t = "title";

● char* s ;

● s = t;

● 并没有产生新的字符串,只是让指针s指向了指针t所指的字符串

● 对s做的所有操作就是对t做的

 8.4.1.2 字符串的输入输出
char string[8];
scanf("%s",string);
printf("%s",string);

scanf读入一个单词(到空格、tab或回车为止)

	char word[8];
	scanf("%s", word);
	printf("%s", word);
hello word
hello
	char word[8];
	char word2[8];
	scanf("%s", word);
	scanf("%s", word2);
	printf("%s#%s#", word,word2);
hello word
hello#word#

只用%s接收容易引起越界异常 ,如果输入的是7个字符正确的接收方式为%7s

	char word[8];
	scanf("%6s", word);
	printf("%s#\n", word);
12345678
123456#
 8.4.1.3 常见错误

char *string;                未初始化

 8.4.1.4 空字符串

char buffer[100] = "";        这是一个空的字符串

	char buffer[100] = "";
	printf("%d", buffer[0] == '\0');
1
D:\C#data\test\x64\Debug\test.exe (进程 12352)已退出,代码为 0。

 char buffer[] = "";        这个数组的长度只有1,放不了任何函数

	char buffer[] = "";
	printf("%d", sizeof(buffer)/sizeof(buffer[0]));
1
D:\C#data\test\x64\Debug\test.exe (进程 15220)已退出,代码为 0。

8.4.2 字符串函数

        C标准库里面有处理字符串的函数,这些函数在头文件string.h里,当需要处理字符串时,需要调用string.h

#include<string.h>
 8.4.2.1 strlen

● size_t strlen(const char*s);

● 返回s的字符串长度(不包括结尾的0)

	char line[] = "hello";
	printf("strlen=%lu\n", strlen(line));
	printf("sizeof=%lu\n", sizeof(line));
strlen=5
sizeof=6
 8.4.2.2 strcmp

● int strcmp(const char *s1,const char *s2)

● 比较字符串,返回:

        ●0:s1=s2

        ●1:   s1>s2

        ●-1:  s1<s2

有些时候返回的是两个字符ASCII码的差值

	char line[] = "a";
	char line2[] = "A";
	printf("%d", strcmp(line, line2));
1
  8.4.2.3 strcpy

●char *strcpy(char *restrict dst,const char *restrict src)

        ●把第二个表达的字符串src拷贝到第一个表达dst的空间里面,不管原来dst里面是什么

        ●restrict表明src和dst不重叠

        ●返回dst

        ●为了能链起代码

   8.4.2.4 strcat

char *strcat(char *restrict s1,const char *restrict s2);

        ●把s2拷到s1的后面,接成一个长的字符串

        ●返回s1

        ●s1必须有足够的空间

        ●cat是catenate的缩写

8.4.2.5 安全问题

●strcpy和strcat都会产生越界风险,安全起见引用了新函数strncpy和strncat,当原指针所指空间没有足够空间,后几位将不会被拷贝过去;

        ●char *strncpy(char* restrict dst,const char * restrictsrc,size_t n);

        ●char *strncat(char* restrict s1,const char * restrictsrc s2,size_t n);

●strncmp可以限定只比较前几个字段

        ●int strncmp(const char *s1,const char *s2,size_t n);

8.4.2.6 其他字符串函数

● 在一段字符串里找某个字符第一次出现的位置

        ●char *strchr(const char *s,int c);

        ●char *strchr(const char *s,int c);        (从右边找过来)

        ●如果字符 c 在字符串 s 中被找到,那么该函数会返回一个指向字符串 s 中首次出现 c 的位置的指针。

        ●如果字符 c 不在字符串 s 中,那么该函数会返回 NULL 指针。

二 C语言程序设计进阶 

1 指针与字符串

1.1指针的使用

1.1.1 指针的使用:指针有什么用?

1.1.1.1 指针的三个作用:

①:用于交换变量

void swap(int *pa,int *pb);
int main() {
    int a = 5;
    int b = 6;
    swap(&a,&b);
    printf("a = %d,b = %d",a,b);
    return 0;
}

void swap(int* pa, int* pb) {
    int t = *pa;
    *pa = *pb;
    *pb = t;
}
a = 6,b = 5

②:指针用于函数返回多个值

● 函数返回多个值,某些值就只能通过指针返回

● 传入的参数实际上是需要保存带回的结果的变量

void minmax(int arr[],int length,int *min,int *max);

int main() {
	int arr[] = {1,2,3,4,5,6,7,8,9,10,11,22,33,44,55};
	int min,max;
	minmax(arr,sizeof(arr)/sizeof(arr[0]),&min,&max);
	printf("min = %d,max = %d", min, max);
}

void minmax(int arr[], int length, int* min, int* max) {
	*min = *max = arr[0];
	for (int i = 1; i < length;i++ ) {
		if (arr[i]>*max) {
			*max = arr[i];
		}
		if (arr[i]<*min) {
			*min = arr[i];
		}
	}
}
min = 1,max = 55

③:指针用于函数返回状态

● 函数返回运算的状态,结果通过指针返回

● 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错

        ●-1或0在文件操作中会大量看到

        ●但是当任何数值都是有效的可能结果时,就得分开返回了

int divide(int a, int b, int* c);

int main() {
	int a = 5;
	int b = 2;
	int c;
	if (divide(a, b, &c)) {
		printf("%d/%d = %d", a, b, c);
	}
}


int divide(int a, int b, int* c) {
	int ret = 1;
	if (b == 0) ret = 0;
	else {
		*c = a / b;
	}
	return ret;
}
1.1.1.2 指针应用场景

1.1.1.3 常见的错误

●定义了指针未对指针初始化

        就是定义了一个指针但是并没有让它指向任何一个地址

1.1.2 指针与数组:为什么数组传进函数后的sizeof不对了?

int main() {
	int arr[] = { 1,2,3,4,5,6,7,8,9,10,11,22,33,44,55 };
	int min, max;
	//查看main中a的大小
	printf("main sizeof(arr) = %lu\n", sizeof(arr));
	//查看main中a的地址
	printf("main p(arr) = %p\n", arr);
	minmax(arr, sizeof(arr) / sizeof(arr[0]), &min, &max);
	printf("min = %d,max = %d", min, max);
}

void minmax(int arr[], int length, int* min, int* max) {

	//查看minmax中a的大小
	printf("minmax sizeof(a) = %lu\n", sizeof(arr));
	//32位架构编译,4刚好是一个指针(地址)的大小

	//查看minmax中a的地址
	printf("minmax p(arr) = %p\n", arr);
	*min = *max = arr[0];
	for (int i = 1; i < length; i++) {
		if (arr[i] > *max) {
			*max = arr[i];
		}
		if (arr[i] < *min) {
			*min = arr[i];
		}
	}
}
main sizeof(arr) = 60
main p(arr) = 000000F6C78FF5B8
minmax sizeof(a) = 8
minmax p(arr) = 000000F6C78FF5B8
min = 1,max = 55

●函数参数表中的数组实际上是指针

●sizeof(a) == sizeof(int*)

●但是可以用数组的运算符[]进行运算

●以下四种函数原型是等价的(只在参数表中等价)

       ●int sum(int *arr,int n)

       ●int sum(int arr[],int n)

       ●int sum(int *,int )

       ●int sum(int [],int n)

数组变量是特殊的指针 

●数组变量本身表达的就是地址,所以

        ●int a[10]; int *p = a;//无需用&取地址

        ●但是数组的单元表达的是变量,需要用&取地址

        ●a == &a[0]        //数组本身的地址和数组的第一个单元的一直相同

●[]运算符可以对数组做,也可以对指针做:

        ●p[0] = a[0]

●数组变量是const的指针,所以不能被赋值

        ●int a[] <==>int *const a =.....

1.1.3 指针与const: 指针本身和所指的变量都有可能const

1.1.4 数组变量和指针的关系

1.2 指针运算

1.2.1 指针运算

1.2.2 动态内存分配

1.2.3 malloc

1.3 字符串操作

1.3.1 单字符输入输出

1.3.2  字符串数组

1.4 字符串函数的实现

1.4.1 函数strlen

1.4.2 函数strcmp

1.4.3 函数strcpy

1.4.4 字符串搜索函数

1.4.5 如何写strcat函数

1.5 编程练习题

总结

基于C语言学习整理的笔记,希望不久后学好C语言!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值