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));//1printf("%zd\n", sizeof(_Bool));//1printf("%zd\n", sizeof(short));//2printf("%zd\n", sizeof(int));//4printf("%zd\n", sizeof(long));//4printf("%zd\n", sizeof(long long));//8printf("%zd\n", sizeof(float));//4printf("%zd\n", sizeof(double));//8printf("%zd\n", sizeof(long double));//8return 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");elseprintf("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);//521while (n){printf("%d ", n % 10);//1 2 5n = 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之间的数字,去试除iint 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~200a + 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);elseprintf("找不到\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);//设置数组内容为-1print_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;elsereturn 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;
}