c语言基础知识

发布于:2024-08-09 ⋅ 阅读:(148) ⋅ 点赞:(0)

ASCII码

字符A~Z的ASCII码值从65~90
• 字符a~z的ASCII码值从97~122
• 对应的⼤⼩写字符(a和A)的ASCII码值的差值32
• 数字字符0~9的ASCII码值从48~57
• 换⾏ \n 的ASCII值是:10
• 在这些字符中ASCII码值从0~31这32个字符是不可打印字符,⽆法打印在屏幕上观察

ASCII码表

字符串和\0(双引号括起来的⼀串字符)

对于字符串"abcdef",我们实际上看到了6个字符:a,b,c,d,e,f,但是实际上在末尾还隐藏⼀个 \0 的转义字符, \0 是字符串的结束标志。所以我们在使⽤库函数 printf() 打印字符串或者
strlen() 计算字符串⻓度的时候,遇到 \0 的时候就⾃动停⽌

#include <stdio.h>
int main()
{
char arr1[] = {'a', 'b', 'c', '\0'};
char arr2[] = "abc";
char arr3[] = {'a', 'b', 'c', '\0'};
printf("%s\n", arr1);//abc
printf("%s\n", arr2);//abc
printf("%s\n", arr3);//abc烫烫烫?^&%*
printf("%s\n", "abc\0def");
return 0;
}

 转义字符

\? :在书写连续多个问号时使⽤,防⽌他们被解析成三字⺟词,在新的编译器上没法验证
• \' :⽤于表⽰字符常量'
• \" :⽤于表⽰⼀个字符串内部的双引号
• \\ :⽤于表⽰⼀个反斜杠,防⽌它被解释为⼀个转义序列符。
• \a :警报,这会使得终端发出警报声或出现闪烁,或者两者同时发⽣。
• \b :退格键,光标回退⼀个字符,但不删除字符。
• \f :换⻚符,光标移到下⼀⻚。在现代系统上,这已经反映不出来了,⾏为改成类似于 \v 
• \n :换⾏符。
• \r :回⻋符,光标移到同⼀⾏的开头。
• \t :制表符,光标移到下⼀个⽔平制表位,通常是下⼀个8的倍数。
• \v :垂直分隔符,光标移到下⼀个垂直制表位,通常是下⼀⾏的同⼀列。
• \ddd :ddd表⽰1~3个⼋进制的数字。如:\130表⽰字符X
• \xdd :dd表⽰2个⼗六进制数字。如:\x30表⽰字符0
\0 :null字符,代表没有内容, \0 就是 \ddd 这类转义字符的⼀种,⽤于字符串的结束标志,其ASCII码值是0.

注释会被替换

编译时,注释会被替换成⼀个空格,所以 min/* 这⾥是注释*/Value 会变成 min Value ,⽽不
是 minValue

数据类型

 字符型                              

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]

浮点型
float(单精度浮点)
double(双精度浮点)
long double(精度更高的浮点型)

布尔类型
Bool
头⽂件 <stdbool.h>
布尔类型变量的取值是:true或者false

sizeof操作符

⽤来计算sizeof操作符数的类型⻓度的,单位是字节

sizeof( 类型 )
sizeof 表达式            

--------sizeof 后边的表达式是不真实参与运算的,根据表达式的类型来得出⼤⼩
--------size_t 类型(无符号整数,打印时用%zd)

sizeof 运算符的返回值,C语⾔只规定是⽆符号整数,并没有规定具体的类型,⽽是留给
系统⾃⼰去决定

sizeof中表达式不计算(sizeof 在代码进⾏编译的时候,就根据表达式的类型确定了,类型的常⽤,⽽表达式的执⾏却要在程序运⾏期间才能执⾏,在编译期间已经将sizeof处理掉了,所以在运⾏期间就不会执⾏表达式了)

printf("%zd\n", sizeof(3 + 3.5));//8

short s = 2;
int b = 10;
printf("%d\n", sizeof(s = b+1));//2    字节存储在short被截断

#include <stdio.h>
int main()
{
	printf("%zd\n", sizeof(char));//1
	printf("%zd\n", sizeof(_Bool));//1
	printf("%zd\n", sizeof(short));//2
	printf("%zd\n", sizeof(int));//4
	printf("%zd\n", sizeof(long));//4
	printf("%zd\n", sizeof(long long));//8
	printf("%zd\n", sizeof(float));//4
	printf("%zd\n", sizeof(double));//8
	printf("%zd\n", sizeof(long double));//8
	return 0;
}

signed和unsigned

C语⾔使⽤ signed 和 unsigned 关键字修饰字符型和整型类型的。
signed 关键字,表⽰⼀个类型带有正负号,包含负值;
unsigned 关键字,表⽰该类型不带有正负号,只能表⽰零和正整数。
对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int 。
由于这是默认情况,关键字 signed ⼀般都省略不写,但是写了也不算错

int 类型也可以不带正负号,只表⽰⾮负整数。这时就必须使⽤关键字 unsigned 声明变量
整数变量声明为 unsigned 的好处是,同样⻓度的内存能够表⽰的最⼤整数值,增⼤了⼀倍
C语⾔规定 char 类型默认是否带有正负号,由当前系统决定。
这就是说, char 不等同于 signed char ,它有可能是 signed char ,也有可能是
unsigned char 。
这⼀点与 int 不同, int 就是等同于 signed int

全局变量:在⼤括号外部定义的变量就是全局变量
        全局变量的使⽤范围更⼴,整个⼯程中想使⽤,都是有办法使⽤的。
局部变量:在⼤括号内部定义的变量就是局部变量
        局部变量的使⽤范围是⽐较局限,只能在⾃⼰所在的局部范围内使⽤的

1.局部变量是放在内存的栈区
2. 全局变量是放在内存的静态区
3. 堆区是⽤来动态内存管理的

 % (计算时的正负号)

返回两个整数相除的余值。这个运算符只能⽤于整数,不能⽤于浮点数

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

#include <stdio.h>
int main()
{
printf("%d\n", 11 % -5); // 1
printf("%d\n",-11 % -5); // -1
printf("%d\n",-11 % 5); // -1
return 0;
}

 占位符

• %a :⼗六进制浮点数,字⺟输出为⼩写。
• %A :⼗六进制浮点数,字⺟输出为⼤写。
• %c :字符。
• %d :⼗进制整数。
• %e :使⽤科学计数法的浮点数,指数部分的 e 为⼩写。
• %E :使⽤科学计数法的浮点数,指数部分的 E 为⼤写

• %i :整数,基本等同于 %d 。
• %f :⼩数(包含 float 类型和 double 类型)。
• %g :6个有效数字的浮点数。整数部分⼀旦超过6位,就会⾃动转为科学计数法,指数部分的 e
为⼩写。
• %G :等同于 %g ,唯⼀的区别是指数部分的 E 为⼤写。
• %hd :⼗进制shortint类型。
• %ho :⼋进制shortint类型。
• %hx :⼗六进制shortint类型。
• %hu :unsignedshortint类型。
• %ld :⼗进制longint类型。
• %lo :⼋进制longint类型。
• %lx :⼗六进制longint类型。
• %lu :unsignedlongint类型。
• %lld :⼗进制longlongint类型。
• %llo :⼋进制longlongint类型。
• %llx :⼗六进制longlongint类型。
• %llu :unsignedlonglongint类型。
• %Le :科学计数法表⽰的longdouble类型浮点数。
• %Lf :longdouble类型浮点数。
• %n :已输出的字符串数量。该占位符本⾝不输出,只将值存储在指定变量之中。
• %o :⼋进制整数。
• %p :指针。
• %s :字符串。
• %u :⽆符号整数(unsignedint)。
• %x :⼗六进制整数。
• %zd : size_t 类型。
• %% :输出⼀个百分号。

输出格式(限定宽度)

允许限定占位符的最⼩宽度

#include <stdio.h>
int main()
{
printf("%5d\n", 123); // 输出为 " 123"
return 0;
}


//%5d 表⽰这个占位符的宽度⾄少为5位。如果不满5位,对应的值的前⾯会添加空格。输出的值默认是右对⻬,即输出内容前⾯会有空格;如果希望改成左对⻬,在输出内容后⾯添加空格,可以在占位符的 % 的后⾯插⼊⼀个-号


printf("%-5d\n", 123); // 输出为 "123 "

 对于⼩数,这个限定符会限制所有数字的最⼩显⽰宽度 

// 输出 " 123.450000"
#include <stdio.h>
int main()
{
printf("%12f\n", 123.45);
return 0;
}


//%12f 表⽰输出的浮点数最少要占据12位。由于⼩数的默认显⽰精度是⼩数点后6位,所以 123.45 输出结果的头部会添加2个空格

 显示正负号

printf() 不对正数显⽰ + 号,只对负数显⽰ - 号。如果想让正数也输出 + 号,可
以在占位符的 % 后⾯加⼀个 +

#include <stdio.h>
int main()
{
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
return 0;
}

 限定小数位数

⼩数点后⾯只保留两位,占位符可以写成 %.2f

最⼩宽度和⼩数位数这两个限定值,都可以⽤ * 代替,通过 printf() 的参数传⼊

#include <stdio.h>
int main()
{
printf("%*.*f\n", 6, 2, 0.5);
return 0;
}
// 等同于printf("%6.2f\n", 0.5);

输出部分字符串

%s 占位符⽤来输出字符串,默认是全部输出。如果只想输出开头的部分,可以⽤ %.[m]s 指定输出的⻓度,其中 [m] 代表⼀个数字,表⽰所要输出的⻓度(占位符 %.5s 表⽰只输出字符串“hello world”的前5个字符,即“hello”)

scanf

scanf() 处理数值占位符时,会⾃动过滤空⽩字符,包括空格、制表符、换⾏符等

⼀个或多个空格不影响 scanf() 解读数据
解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌
scanf() 的返回值是⼀个整数,表⽰成功读取的变量个数。
如果没有读取任何项,或者匹配失败,则返回 0 。如果在成功读取任何数据之前,发⽣了读取错误或者遇到读取到⽂件结尾,则返回常量EOF

占位符

• %c :字符
• %d :整数。
• %f : float 类型浮点数。
• %lf : double 类型浮点数。
• %Lf : long double 类型浮点数。
• %s :字符串。
• %[] :在⽅括号中指定⼀组匹配的字符(⽐如 %[0-9] ),遇到不在集合之中的字符,匹配将会停⽌。

上⾯所有占位符之中,除了 %c 以外,都会⾃动忽略起⾸的空⽩字符。 %c 不忽略空⽩字符,总是返回当前第⼀个字符,⽆论该字符是否为空格。
如果要强制跳过字符前的空⽩字符,可以写成 scanf(" %c", &ch) ,即 %c 前加上⼀个空格,表⽰跳过零个或多个空⽩字符。
下⾯要特别说⼀下占位符 %s ,它其实不能简单地等同于字符串。它的规则是,从当前第⼀个⾮空⽩字符开始读起,直到遇到空⽩字符(即空格、换⾏符、制表符等)为⽌。
因为 %s 不会包含空⽩字符,所以⽆法⽤来读取多个单词,除⾮多个 %s ⼀起使⽤。这也味着,scanf() 不适合读取可能包含空格的字符串,⽐如书名或歌曲名。另外, scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符 \0 。
scanf() 将字符串读⼊字符数组时,不会检测字符串是否超过了数组⻓度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防⽌这种情况,使⽤ %s 占位符时,应该指定读⼊字符串的最⻓⻓度,即写成 %[m]s ,其中的 [m] 是⼀个整数,表⽰读取符串的最⼤⻓度,后⾯的字符将被丢弃
 

赋值忽略符

#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d-%d-%d", &year, &month, &day);
printf("%d %d %d\n", year, month, day);
return 0;
}


//如果⽤⼾输⼊ 2020-01-01 ,就会正确解读出年、⽉、⽇。问题是⽤⼾可能输⼊其他格式,⽐如 2020/01/01 ,这种情况下, scanf() 解析数据就会失败

//只要把 * 加在任何占位符的百分号后⾯,该占位符就不会返回值,解析后将被丢弃
//scanf("%d%*c%d%*c%d", &year, &month, &day);

分支语句

if语句

如果表达式的结果为真,则语句执行。


在C语言中如何表示真假?
0表示假,非0表示真。

语法结构:
if(表达式) 语句;
if(表达式) 
    语句1;
else
    语句2;


//多分支
if(表达式1)
    语句1;
else if(表达式2)
    语句2;
else
    语句3;


//如果条件成立,要执行多条语句
#include <stdio.h>
int main()
{
if(表达式)
{
语句列表1;
}
else
{
语句列表2;
}
return 0;
}
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if(age<18)
{
printf("少年\n");
}
else if(age>=18 && age<30)
{
printf("青年\n");
}
else if(age>=30 && age<50)
{
printf("中年\n");
}
else if(age>=50 && age<80)
{
printf("老年\n");
}
else
{
printf("老寿星\n");
}
}

悬空else(else是和它离的最近的if匹配的)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int a = 0;
	int b = 2;
	if (a == 1)//当满足该条件才会进入下一个if语句
		if (b == 2)
			printf("hehe\n");
		else
			printf("haha\n");
	return 0;
}

//改进:加大括号
#include <stdio.h>
int main()
{
    int a = 0;
    int b = 2;
if(a == 1)
{
    if(b == 2)
    {
        printf("hehe\n");
    }
}
else
{
    printf("haha\n");
}
    return 0;
}

练习
1. 判断一个数是否为奇数

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	size_t n = 0;
	scanf("%zd", &n);
	if (n % 2 == 0)
	{
		printf("%zd是偶数", n);
	}
	else
	{
		printf("%zd是奇数", n);
	}
}

2. 输出1-100之间的奇数 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	for (int i = 1; i <= 100; i++)
	{
		if (i % 2 != 0)
		{
			printf("%d ", i);
		}
	}
}

switch语句

switch(整型表达式)
{
    case 整形常量表达式:
    语句;
}

break语句 的实际效果是把语句列表划分为不同的分支部分
在最后一个 case 语句的后面加上一条 break语句。
(之所以这么写是可以避免出现在以前的最后一个 case 语句后面忘了添加 break语句)

不想忽略不匹配所有标签的表达式的值时该怎么办呢?
你可以在语句列表中增加一条default子句,每个switch语句中只能出现一条default子句
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int day = 0;
	switch (day)
	{
		case 1:
			printf("星期一\n");
			break;
		case 2:
			printf("星期二\n");
			break;
		case 3:
			printf("星期三\n");
			break;
		case 4:
			printf("星期四\n");
			break;
		case 5:
			printf("星期五\n");
			break;
		case 6:
			printf("星期六\n");
			break;
		case 7:
			printf("星期天\n");
			break;
	}
	return 0;
}



//改进
//1. 输入1-5,输出的是“weekday”;
//2. 输入6-7,输出“weekend”

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int day = 0;
	switch (day)
	{
				case 1:
				case 2:
				case 3:
				case 4:
				case 5:
					printf("weekday\n");
					break;
				case 6:
				case 7:
					printf("weekend\n");
					break;
	}
	return 0;
}

循环语句


while

while(表达式)
循环语句;
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
printf("%d ", i);//1 2 3 4 5 6 7 8 9 10
i = i+1;
}
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);//521

	while (n)
	{
		printf("%d ", n % 10);//1 2 5
		n = n / 10;
	}

	return 0;
}

while语句中的break和continue

break在while循环中的作用:
其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。
所以:while中的break是用于永久终止循环的

continue在while循环中的作用就是:
continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,
而是直接跳转到while语句的判断部分。进行下一次循环的入口判断

for循环

for(表达式1; 表达式2; 表达式3)
语句;//如果循环体想包含更多的语句,可以加上⼤括号

//表达式1⽤于循环变量的初始化
//表达式2⽤于循环结束条件的判断
//表达式3⽤于循环变量的调整
#include <stdio.h>
int main()
{
    int i = 0;
    for(i=1; i<=10; i++)
    {
        printf("%d ", i);
    }
    return 0;
}

do-while循环

do
语句;
while(表达式);
#include <stdio.h>
int main()
{
    int i = 1;
    do
    {
        printf("%d ", i);
        i = i + 1;

    }while(i<=10);

    return 0;
}

break和continue语句

break 的作⽤是⽤于永久的终⽌循环,只要 break 被执⾏,直接就会跳出循环,继续往后执

continue 的作⽤是跳过本次循环 continue 后边的代码,在 for 循环和 while 循环中有所
差异的

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int i = 1;
	while (i <= 10)
	{
		if (i == 5)
			break;//当i等于5后,就执⾏break,循环就终⽌了
		printf("%d ", i);
		i = i + 1;
	}
	return 0;
}

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int i = 1;
	while (i <= 10)
	{
		if (i == 5)
			continue;
		//当i等于5后,就执⾏continue,直接跳过continue的代码,去循环的判断的地⽅
		//因为这⾥跳过了i = i+1,所以i⼀直为5,程序陷⼊和死循环
		printf("%d ", i);
		i = i + 1;
	}
	return 0;
}

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int i = 1;
	for (i = 1; i <= 10; i++)
	{
		if (i == 5)
			break;
		printf("%d ", i);
	}
	return 0;
}

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int i = 1;
	for (i = 1; i <= 10; i++)
	{
		if (i == 5)
			continue;//这⾥continue跳过了后边的打印,来到了i++的调整部分
		printf("%d ", i);
	}
	return 0;
}

for循环嵌套

判断是否为素数

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	for (int i = 100; i <= 200; i++)
	{
		int flag = 1;//标记是否为素数
		for (int j = 2; j < i; j++)
		{
			if (i % j == 0) 
			{
				flag = 0;
				break;//能被除1和自身之外的数整除,不是素数
			}
		}
		if (flag == 1) printf("%d ", i);
	}
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <math.h>
//sqrt- 库函数- 开平方的
int main()
{
	//产生100~200之间的数
	int i = 0;
	int count = 0;
	for (i = 101; i <= 200; i += 2)
	{
		int flag = 1;//假设i是素数

		//产生的i就是100到200之间的数字
		//每次循环进来产生一个i,这个时候判断i是否是素数
		//方法是:产生2~i-1之间的数字,去试除i
		int j = 0;
		for (j = 2; j <= sqrt(i); j++)
		{
			if (i % j == 0)
			{
				flag = 0;//表示i不是素数
				break;
			}
		}
		if (flag == 1)
		{
			printf("%d ", i);
			count++;
		}
	}

	printf("\ncount = %d\n", count);
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int i = 0;
	int j = 0;
	for (i=0; i < 5; i++)
	{
		for (j=0; j < 5; j++)
		{
			printf("hehe\n");
		}
	}

	return 0;
}

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int i = 0;
	int j = 0;
	for (; i < 5; i++)
	{
		for (; j < 5; j++)//i=0时,进入第二层循环,当j=5跳出循环,当i=1时,j仍然为5,进入不了第 
                         //二层循环
		{
			printf("hehe\n");
		}
	}

	return 0;
}

goto语句

goto 语句可以实现在同⼀个函数内跳转到设置好的标号处

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	printf("hehe\n");
	goto next;
	printf("haha\n");
next:
	printf("跳过了haha的打印\n");
	return 0;
}

猜数字游戏

rand

头⽂件是:stdlib.h

C语⾔提供了⼀个函数叫rand,这函数是可以⽣成随机数的
int rand (void);

rand函数会返回⼀个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的⼤⼩是依赖编译器上实现的,但是⼤部分编译器上是32767

#include <stdio.h>
#include <stdlib.h>
int main()
{
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	return 0;
}

rand函数⽣成的随机数是伪随机的,伪随机数不是真正的随机数,是通过某种算法⽣成的随机数。真正的随机数的是⽆法预测下⼀个值是多少的。⽽rand函数是对⼀个叫“种⼦”的基准值进⾏运算⽣成的随机数,之所以前⾯每次运⾏程序产⽣的随机数序列是⼀样的,那是因为rand函数⽣成随机数的默认种⼦是1。如果要⽣成不同的随机数,就要让种⼦是变化的(故srand)

srand

⽤来初始化随机数的⽣成器的
void srand (unsigned int seed);
程序中在调⽤rand函数之前先调⽤srand函数,通过srand函数的参数seed来设置rand函数⽣成随机数的时候的种⼦,只要种⼦在变化,每次⽣成的随机数序列也就变化起来了
给srand的种⼦是如果是随机的,rand就能⽣成随机数;在⽣成随机数的时候⼜需要⼀个
机数
,这就⽭盾了(故引申出时间time)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
	//使⽤time函数的返回值设置种⼦
	//因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
	srand((unsigned int)time(NULL));
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	return 0;
}

 

time

头⽂件:time.h

程序中我们⼀般是使⽤程序运⾏的时间作为种⼦的,因为时间时刻在发⽣变化的
time_t time (time_t* timer);
time函数会返回当前的⽇历时间,其实返回的是1970年1⽉1⽇0时0分0秒到现在程序运⾏时间之间的差值,单位是秒。返回的类型是time_t类型的,time_t类型本质上其实就是32位或者64位的整型类型。
time函数的参数timer如果是⾮NULL的指针的话,函数也会将这个返回的差值放在timer指向的内存中带回去。
如果timer是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳(time(NULL);//调⽤time函数返回时间戳,这⾥没有接收返回值)

设置随机数的范围

rand() %100;//余数的范围是0~99
rand()%100+1;//%100的余数是0~99,0~99的数字+1,范围是1~100
100 + rand()%(200-100+1)//余数的范围是0~100,加100后就是100~200

a + rand()%(b-a+1)//⽣成a~b的随机数

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


//函数
void menu()
{
	printf("************************\n");
	printf("******  1. play  *******\n");
	printf("******  0. exit  *******\n");
	printf("************************\n");
}

//猜数字游戏的实现
void game()
{
	//1. 生成随机数(1~100)
	int ret = rand() % 100 + 1;//1~100
	//n%100 余数的取值的范围 0~99
	//2. 猜数字
	int guess = 0;
	int count = 5;
	while (count)
	{
		printf("请猜数字:>");
		scanf("%d", &guess);
		if (guess < ret)
		{
			printf("猜小了\n");
		}
		else if (guess > ret)
		{
			printf("猜大了\n");
		}
		else
		{
			printf("恭喜你,猜对了\n");
			break;
		}
		count--;
	}

	if (count == 0)
	{
		printf("猜失败了,正确的数字是:%d\n", ret);
	}
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//种子设置只需要一次

	do
	{
		menu();

		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}

	} while (input);

	return 0;
}

 数组

数组是⼀组相同类型元素的集合
数组中存放的是1个或者多个数据,但是数组元素个数不能为0
数组中存放的多个数据,类型是相同的

type arr_name[常量值];
存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的⼤⼩和数组的元素类型

初始化

//完全初始化
int arr[5] = {1,2,3,4,5};
//不完全初始化
int arr2[6] = {1};//第⼀个元素初始化为1,剩余的元素默认初始化为0
//错误的初始化 - 初始化项太多
int arr3[3] = {1, 2, 3, 4};

 ⼀维数组在内存中的存储

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("&arr[%d] = %p\n ", i, &arr[i]);
	}
	return 0;
}

数组随着下标的增⻓,地址是由⼩到⼤变化的,并且我们发现每两个相邻的元素之间相差4(因为⼀个整型是4个字节)。数组在内存中是连续存放的

sizeof计算数组元素个数(计算类型或者变量⼤⼩,计算数组的⼤⼩)

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%d\n", sizeof(arr));
	return 0;
}
//输出的结果是40,计算的是数组所占内存空间的总⼤⼩,单位是字节

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%d\n", sizeof(arr[0]));//计算⼀个元素的⼤⼩,单位是字节
	return 0;
}

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	return 0;
}

二维数组

type arr_name[常量值1][常量值2];

初始化

//不完全初始化

int arr1[3][5] = {1,2};
int arr2[3][5] = {0};
 

//完全初始化

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

//按照⾏初始化

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

//初始化时省略⾏,但是不能省略列

int arr5[][5] = {1,2,3};
int arr6[][5] = {1,2,3,4,5,6,7};
int arr7[][5] = {{1,2}, {3,4}, {5,6}};
 

⼆维数组在内存中的存储

#include <stdio.h>
int main()
{
	int arr[3][5] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
		}
	}
	return 0;
}

每⼀⾏内部的每个元素都是相邻的,地址之间相差4个字节,跨⾏位置处的两个元素(如:arr[0][4]和arr[1][0])之间也是差4个字节,所以⼆维数组中的每个元素都是连续存放的

C99中的变⻓数组

在C99标准之前,C语⾔在创建数组的时候,数组⼤⼩的指定只能使⽤常量、常量表达式,或者如果我们初始化数据的话,可以省略数组⼤⼩。

int arr1[10];
int arr2[3+5];
int arr3[] = {1,2,3};

C99中给⼀个变⻓数组(variable-lengtharray,简称VLA)的新特性,允许我们可以使⽤变量指定数组⼤⼩

int n = a+b;
int arr[n];

//数组 arr 就是变⻓数组,因为它的⻓度取决于变量 n 的值,编译器没法事先确定,只
有运⾏时才能知道 n 是多少
//变⻓数组的根本特征,就是数组⻓度只有运⾏时才能确定,所以变⻓数组不能初始化。它的好处是程
序员不必在开发时,随意为数组指定⼀个估计的⻓度,程序可以在运⾏时为数组分配精确的⻓度。有
⼀个⽐较迷惑的点,变⻓数组的意思是数组的⼤⼩是可以使⽤变量来指定的,在程序运⾏的时候,根
据变量的⼤⼩来指定数组的元素个数,⽽不是说数组的⼤⼩是可变的。数组的⼤⼩⼀旦确定就不能再
变化了
//VS2022上,虽然⽀持⼤部分C99的语法,没有⽀持C99中的变⻓数组,没法测试;
#include <stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);//根据输⼊数值确定数组的⼤⼩
	int arr[n];
	int i = 0;
	for (i = 0; i < n; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}//gcc

数组练习

练习1:多个字符从两端移动,向中间汇聚
 

#include <stdio.h>
int main()
{
	char arr1[] = "welcome to bit...";
	char arr2[] = "#################";
	int left = 0;
	int right = strlen(arr1) - 1;
	printf("%s\n", arr2);
	while (left <= right)
	{
		Sleep(1000);
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		left++;
		right--;
		printf("%s\n", arr2);
	}
	return 0;
}

练习2:⼆分查找

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int left = 0;
	int right = sizeof(arr) / sizeof(arr[0]) - 1;
	int key = 7;//要找的数字
	int mid = 0;//记录中间元素的下标
	int find = 0;
	while (left <= right)
	{
		mid = (left + right) / 2;
        //mid = left+(right-left)/2;
		if (arr[mid] > key)
		{
			right = mid - 1;
		}
		else if (arr[mid] < key)
		{
			left = mid + 1;
		}
		else
		{
			find = 1;
			break;
		}
	}
	if (1 == find)
		printf("找到了,下标是%d\n", mid);
	else
		printf("找不到\n");
}

函数

库函数

库函数⽂档的⼀般格式
1. 函数原型
2. 函数功能介绍
3. 参数和返回类型说明
4. 代码举例
5. 代码输出
6. 相关知识链接

⾃定义函数

ret_type fun_name(形式参数)
{}

//ret_type 是函数返回类型
//fun_name 是函数名
//括号中放的是形式参数
//{}括起来的是函数体
#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	//输⼊
	scanf("%d %d", &a, &b);
	//调⽤加法函数,完成a和b的相加
	//求和的结果放在r中
	int r = Add(a, b);
	//输出
	printf("%d\n", r);
	return 0;
}

形参和实参

x和y(形参)确实得到了a和b(实参)的值,但是x和y的地址和a和b的地址是不⼀样的,所以形参是实参的⼀份临时拷⻉

形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化

对形参的修改不会影响实参

return语句

return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果
return后边也可以什么都没有,直接写 return;适合函数返回类型是void的情况
return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型
return语句执⾏后,函数就彻底返回后边的代码不再执⾏
如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误

数组做函数参数

• 函数的形式参数要和函数的实参个数匹配
• 函数的实参是数组,形参也是可以写成数组形式
• 形参如果是⼀维数组,数组⼤⼩可以省略不写
• 形参如果是⼆维数组⾏可以省略,但是列不能省略
• 数组传参,形参是不会创建新的数组的
• 形参操作的数组和实参的数组是同⼀个数组​​​​​​​

#include <stdio.h>
void set_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr[i] = -1;
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	set_arr(arr, sz);//设置数组内容为-1
	print_arr(arr, sz);//打印数组内容
	return 0;
}

 嵌套调⽤和链式访问

#include <stdio.h>
int is_leap_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return 1;
	else
		return 0;
}
int get_days_of_month(int y, int m)
{
	int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int day = days[m];
	if (is_leap_year(y) && m == 2)
		day += 1;
	return day;
}
int main()
{
	int y = 0;
	int m = 0;
	scanf("%d %d", &y, &m);
	int d = get_days_of_month(y, m);
	printf("%d\n", d);
	return 0;
}

链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问

#include <stdio.h>
int main()
{
	printf("%d\n", strlen("abcdef"));//链式访问
	return 0;
}
#include <stdio.h>
int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}
//4321
//第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个printf的返回值。
//第三个printf打印43,在屏幕上打印2个字符,再返回2,第⼆个printf打印2,在屏幕上打印1个字符,//再放回1,第⼀个printf打印1

static和extern

static 静态
• 修饰局部变量
• 修饰全局变量
• 修饰函数

extern 是⽤来声明外部符号

作⽤域
限定这个名字的可⽤性的代码范围就是这个名字的作⽤域
1. 局部变量的作⽤域是变量所在的局部范围。
2. 全局变量的作⽤域是整个⼯程(项⽬)


⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。
1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束。
2. 全局变量的⽣命周期是:整个程序的⽣命周期

static修饰局部变量 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test()
{
	int i = 0;
	i++;
	printf("%d ", i);
}
int main()
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		test();
	}
	return 0;
}

#include <stdio.h>
void test()
{
	//static修饰局部变量
	static int i = 0;
	i++;
	printf("%d ", i);
}
int main()
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		test();
	}
	return 0;
}

static修饰全局变量

extern是⽤来声明外部符号的,如果⼀个全局的符号在A⽂件中定义的,在B⽂件中想使⽤,就可以使⽤ extern 进⾏声明,然后使⽤

//add.c
int g_val = 2018;
//test.c
#include <stdio.h>
extern int g_val;
int main()
{
    printf("%d\n", g_val);
    return 0;
}

⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤不能在其他源⽂件内使⽤
本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。
使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤static修饰

static修饰函数

本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤。
使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰

//add.c
int Add(int x, int y)
{
    return x+y;
}
//test.c
#include <stdio.h>
extern int Add(int x, int y);
int main()
{
    printf("%d\n", Add(2, 3));
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到