C语言专业总结
-----编译器环境Visual Stdio
戴学宜
1.基础语法
(1)输出Hello World
#include <Stdio.h>
int main(){
printf("Hello World\n");
return 0;
}
C | C语言风格代码 |
---|---|
头文件包含 | #include <Stdio.h> |
输出HelloWorld | printf(“Hello World”); |
解析:
头文件stdio中包含输入输出函数,scanf和printf函数,此外还有很多其他函数
int main() 代表的是主函数,是一个程序比不可少的一部分,依次分为返回值类型,名字,参数,
返回值类型代表这个函数想要返回的数的类型,如float(单精度),double(双精度),short int(短整型)对应的也有long int(长整型)或long long int(长长整型) ,char(字符型),等等
其中参数这里默认为空,为形参,调用的是实参,这些会在函数章节讲到.
return 0; 代表是返回0代表程序结束,或者可以为一个特定值,如return 2;代表返回一个整数2,返回的含义也同样会在函数中讲到,暂且理解为给程序返回一个数0,代表结束程序
(2)变量名起名规范
变量也成为标识符,是存储数据的一个容器,如 int a = 5;定义了一个变量名a,存放了一个整型5.同时也定义为了整型变量
(1)变量名是由字母,数字,下划线构成的
(2)不能是数字开头如:123number
(3)数据类型
判断数据类型所占字节大小可以用sizeof()函数判断
如(此处就只写语句了,基本语句请自打)
int a;
printf("%d",sizeof(a));
输出结果为4,所以得知int类内存字节大小为4,即编译器在内存空间开辟了4个字节大小的空间供a变量使用
数据类型可分为两类:数值类和非数值类(字符型等等),其中还有一个特殊的类型,bool(布尔类型),我们在下面会说到
数值型:
int ,short int ,long int ,long long int ;
float ,double(此两种可理解为小数点型,float为单精度,double为双精度)
问:如果一个数1.3,他的数据类型是什么呢?—注意此处只是打个比方,实际中1.3应该赋值给一个变量,通常我们用 double型
答:默认是为double类型的,如果你想要强制类型转换为float类型,你可以用(float)1.3或1.3f都可以转换为单精度型
强制转换特别说明:
如果你想要将一个占用字节数小的数据类型转为占用字节数大的数据类型是没有的问题的,
如:原本是short int 类型(2字节)转int(4字节)是没有问题的
但是当你将一个占用字节数大的数据类型转为一个占用字节数小的数据类型可能会发生错误
如:double(8字节)->float(4字节)就可能有错误发生.
下面是代码演示:将一个整型转为浮点型数据
#include <stdio.h>
int main() {
int a = 5;
printf("%lf", (double)a);
return 0;
}
输出结果为:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xCSUjXMs-1651931314325)(E:\C语言学习笔记\图片\强制类型转换.png)]
可以清楚看到变为了双精度数据类型了
非数值型
char 即字符型
赋值方法:
char ch = ‘a’;
此时ch是一个字符变量,它存储一个字符数据a。
注意区分a与‘a’的区别,后者才是字符,采用的是ASCII编码。
bool类型
C语言本身里面没有专门的bool类型,C++里面才有。在C99标准还没出来时,是用整数0代表“假”,1代表“真”。
在C99中提供了_Bool类型
赋值方法:
_Bool b;//声明一个布尔型变量
//布尔型实际是一个无符号整型(指的是0和正整数,没有负整数),因此布尔型变量实际上就是整型变量,但是与普通的整型变量不同,因为布尔型变量只能赋值为0或1(假或真).
b = 0;
printf("%d\n",b);//显示0,表示假。
b = 8;
printf("%d",b);//虽然赋值为8,但是只要是非零值在布尔类型眼里就是1,所以显示1,表示真
C99中还提供了一个stdbool.h头文件,能让我们操作布尔值更容易。该头文件中用bool来代替_Bool,还提供true和false两个宏,分别代表1或0(真或假)
#include <stdbool.h>
int main(){
bool a;
a = false;
printf("%d",a);//显示0,表示假
a = true;
printf("%d",a);//显示1,表示真
}
(4)标准输入输出
标准输入
特别说明:如果你也用的是VS,有可能你用scanf并且你的文件拓展名是cpp时(此为C++文件拓展名)可能会报错,有以下几种解决方法
-
解决方法 在头文件下宏定义一个值 即
#define _CRT_SECURE_NO_WARNINGS
-
解决方法 用.c的文件拓展名即可.
-
右击项目,选择属性,在c/c++中在其他选项中输入 /D_CRT_SECURE_NO_WARNINGS 即可
如图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JKhppb1z-1651931314326)(E:\C语言学习笔记\图片\scanf.png)]
标准输入格式scanf(“%xxx”,&变量名);
&为取地址运算符,是必不可少的
其中xxx代表的只是你想键入的数据类型而已,变量名即你想存放的容器的名称.
比如我不想以后给整型变量a和双精度变量b每次赋初始值,我想手动输入。
代码为:
#include <Stdio.h>int main() {
int a = 5;
double b;
scanf("%d%lf", &a, &b);
printf("%lf\n",(double)a);
printf("%f",b);
return 0;
}
结果为:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dszPqlha-1651931314326)(E:\C语言学习笔记\图片\scanf输出.png)]
注意点:声明了一个整型变量那么输入的类型也应该一致!
常用格式符 | 含义 |
---|---|
%d | 整型int |
%f | 单精度浮点型float |
%lf | 双精度浮点型double |
%c | 字符型char |
%s | 字符串型(由若干个字符构成的序列)string |
%o | 以八进制形式输入整数 |
%x或%X | 以十六进制形式输入整数 |
%g或%G | 以小数或指数形式输入浮点数 |
特殊输入格式(不推荐)
#include <Stdio.h>
int main() {
int a = 5;
double b;
scanf("a=%d,b=%lf", &a, &b);
printf("%lf\n",(double)a);
printf("%f",b);
return 0;
}
可以清楚看到,你必须要将前面的字符一个个打出来才能正常显示出来,不然会有意想不到的结果出现,当然这种输入格式并不推荐[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmPRWjd7-1651931314326)(E:\C语言学习笔记\图片\特殊scanf输入.png)]
输出格式
完整输出格式
% 标志 最小字段宽度 精度 修饰符 格式符
printf(“%d”,n);//其中双引号中的内容可以是上面的内容,n为你想输入的变量名(当然也可以是表达式,不过有多个关于同个变量名的表达式需要注意,请学到自增运算符时注意此种情况的说明).
其中格式符时最为重要的!
% | 就是个占位符的作用 |
---|---|
标志 | -,+,# |
最小字段宽度 | 见解析 |
精度 | 见解析 |
修饰符 | h(短整型),l(长整型),ll(长长整型),L(长双精度型) |
格式符 | 上节以说 |
标志详细说明
标志 | 含义 |
---|---|
- | 输出时靠左对齐 即贴紧左边窗口 (啥都不带时默认是左对齐)与最小字段宽度搭配效果明显 |
+ | 这并不是强制右对齐的意思了,而是让你输出的数如果是正数则在前面带个+号,负数带个-号,与平常输出差别主要在于正数,负数无太大去呗 |
# | 输出八进制数时以0开头,输出十六进制以0x或0X开头,浮点数始终有小数点,保留以格式g或G输出的数的尾部零 |
最小字段宽度的含义
它指定了数据输出的宽度,如果你所输入的数据太小无法达到这一宽度,则会用空格进行填充,如果数据太大超过了这一宽度,则会按原样输出.如果宽度为*,表示宽度由输出项指明.
以下代码将-标志与最小字段宽度结合使用
#include <Stdio.h>
int main() {
printf("%d\n", 123);
//最小字段宽度
printf("%5d\n", 123);
//-标志与最小字段宽度的结合(多出来的两个空格仍然存在)
printf("%-5d\n", 123);
printf("%-5d%-5d\n", 123, 456);
return 0;
}
结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2GX4poNJ-1651931314326)(E:\C语言学习笔记\图片\最小字段宽度.png)]
以下代码将+标志与最小字段宽度结合使用
#include <Stdio.h>
int main() {
printf("%d\n", 123);
//最小字段宽度
printf("%5d\n", 123);
//+标志与最小字段宽度的结合
printf("%+5d\n", 123);
printf("%+5d%+5d\n", 123, 456);
printf("%+5d%+5d\n", 123, -456);
return 0;
}
结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTEu5CCk-1651931314327)(E:\C语言学习笔记\图片+与最小字段宽度.png)]
以下代码将最小字段宽度与**#标志与格式符**结合使用
#include <Stdio.h>
int main() {
printf("%d\n", 123);
//最小字段宽度与格式符结合使用
printf("%5d\n", 123);
printf("%5o\n", 123);
printf("%5x\n", 123);
printf("%7g\n", 123.51);//123.51占了6位,包括小数点
//最小字段宽度与#标志与格式符结合使用
printf("%#5o\n", 123);
printf("%#5x\n", 123);
printf("%#5g\n", 123.515);
return 0;
}
结果为:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vd3JsCji-1651931314327)(E:\C语言学习笔记\图片\最小字段与#标志.png)]
精度含义:精度的含义依赖于运算符,实际中我们用的最多的即于f,或lf一起搭配使用了
%.xxf或%.xxlf都是常用的,xx代表你想输入的数字
#include <Stdio.h>
int main() {
printf("%d\n", 123);
printf("%.2f", 123.123);
return 0;
}
结果为
123
123.12
即后面指挥让他小数点后xx位输出出来
修饰符(不常用)
修饰符 | 常用修饰的格式符 | 含义 |
---|---|---|
h | d,o,x,X | 短整型(short) |
l | d,o,x,X | 长整型(long) |
ll | d,o,x,X | 长长整型(long long) |
L | f,e,E,g,G | 长双精度型(long double) |
(5)运算符
常用运算符
优先级表
按优先级排序(即运算时最先执行那一部分)
优先级 | 运算符名称 | 运算符 |
---|---|---|
1 | 括号运算符 | ( ) |
1 | 下标运算符 | [ ] (用于数组) |
1 | 自增运算符(后缀) | ++ |
1 | 自减运算符(后缀) | – |
2 | 自增运算符(前缀) | ++ |
2 | 自减运算符(前缀) | – |
2 | 地址运算符 | & |
2 | 指针运算符(之后会将) | * |
2 | 正号运算符 | + |
2 | 负号运算符 | - |
2 | 逻辑运算符 | !(取反) |
4 | 算术运算符 | * ,/ ,%(取余) |
5 | 算术运算符 | +,- |
7 | 关系运算符 | <,>,<=,>= |
8 | 关系运算符 | ==(等于),!=(不等于) |
12 | 逻辑运算符 | && |
13 | 逻辑运算符 | || |
14 | 条件运算符 | ? :(三元运算符) |
15 | 赋值运算符 | =,*=,/=,%=,+=,-=,<<=,>>=,&=,^=,!= |
解析:
自增运算符:
重点注意!!!
其中多个关于同个变量名的表达式的输入顺序是不一样的.
#include <Stdio.h>
int main(){
int n = 1,m = 2;
int count = 0;
//后是结果
printf("%d%d",n,m);//1,2
printf("%d %d %d",count++,count++,count++);//2 1 0
}
这是可能会有读者不理解,为什么不是0 1 2,根据函数调用原则,最后的参数先进栈(可以理解为存放一个局部变量的容器),即通俗的说就是从右往左进行运算赋值。所以就是为什么 2 1 0 的原因了.
假设一个变量a,
++a,–a都等价于a += 1,a=a+1,a -= 1.a = a -1
而a++,a–,其实是指先将a本身的值先用掉(例如赋值给b,或输出等等),再加1或减1
#include <Stdio.h>
int main() {
int a = 1;
printf("%d\n", a++);
printf("%d\n", a);
printf("%d", ++a);
return 0;
}
结果:
1
2
3
先输出a本身的值1,再加1
为2了,再输出,
再加1,输出,为3了
取余运算符%与整除运算符/
整除运算符
在C语言中如果直接将两个整数相除,得到的是他们取整的部分,如果你想要小数部分,你需要将相除的两个数或任意一个变为浮点数,输出类型也与之对应即可,如下列代码
取余运算符,即取出两数整除的余数部分.
#include <Stdio.h>
int main() {
int a = 5;
printf("%d\n", a / 2);
printf("%f\n", a / 2.0);
printf("%d\n", a % 2);
return 0;
}
结果:
2
2.500000
1
关系运算符(返回值是0或1)
重点是==,!=的概念
这与数学中的=不同,应该C中的=已经被用于赋值了,所以为了避免混淆,采用了==的方式表达,输出的值是1(真)即两个数相等,或0(假,两个数不相等)
!=即不相等,输出的值与==相同,只是它是两个数真的不相同的时候输出1,两个数相同得到时候为假输出0
如5 != 5 是0,翻译成话,就是5不等于5? 错0
7 == 7 是1 7等于7? 对1
逻辑运算符(返回值是0或1)
!(取反),&&(与),||(或)
例如
int a = 5,b = 4;
int c = 0;
a && b 其实就是判断两边的数是否都为真即都不为0,
c && b 如果两者有1个为0,最后结果就是0
a || c 两者之中只要有1个数为1,最后结果就是1,两数都为0,结果也为0
!a 取反操作即如果你数不是0,取反之后数就是0
!c 原本的数是0,取反之后就是1
注意!!!:C语言中这里有陷阱,
&& 情况
例如a && b,如果a为0,那么右边的表达式时不会执行的(C语言:你左边都是0了,整体肯定是0,还算右边干嘛)
比如 a为0的情况下,a && ++b 执行之后,输出0,之后想看看b的值,这时b的值还是4,不会是5
|| 情况
例如a || b,如果a为1,那么右边的表达式时不会执行的(C语言:你左边都是1了,整体肯定是1.还算右边干嘛)
比如 a为1的情况下,a || ++b 执行之后,输出1,之后想看看b的值,这时b的值还是4,不会是5
#include <Stdio.h>
int main() {
int a = 5,b = 4;
int c = 0;
//注释是结果
printf("%d\n", a && b);//1
printf("%d\n", c && b);//0
printf("%d\n", a || c);//1
printf("%d\n", !a);//0
printf("%d\n", !c);//1
return 0;
}
三元运算符
元代表一个表达式,顾名思义有3个表达式
表达式1?表达式2:表达式3
表达式1的结果要是一个bool值,即真或假,如果为真则执行表达式2,如果为假,则执行表达式3
读者可以自行理解一下,还可以有嵌套结构,如果a>b则执行嵌套中的,否则为3
#include <Stdio.h>
int main() {
int a = 5,b = 4;
int c = 0;//注释是结果
printf("%d\n", a > b ? 123 : 456);//123
printf("%d\n", c > b ? 123 : 456);//456
printf("%d\n", a > b ? (a > c ? 1 : 2) : 3);//1
return 0;
}
(6)标准库函数
注意:(里面的 *变量名 是指针,里面存放的是一个内存地址值,后面会详讲,大致了解下即可,不用死记下来)
常用标准库函数如 stdio.h,math.h,string.h,ctype.h.stdlib.h,system.h
1、stdio库:主要功能是输入输出操作,包括文件输入输出,标准输入输出。
输出函数: int puts(const char* str) 把一个字符串写出到标准输出
int printf(char *format,args,…) 把args,…的值以format指定的格式输出到标准输出设备,输出字符的个数
用\0替换为\n换行符
输入函数:char* gets(char* str)从标准输入读一行数据知道换行符才结束
FILE *fopen(char *filename,char *mode) 以mode指定的方式打开名为filename的文件,返回值:成功,返回文件指针(文件信息区的起始地址),否则返回NULL
将\n替换为\0
2、ctype库
ps:以下几个表格来自于网上资料
调用字符函数时,要求在源文件中包下以下命令行:
\ #include <ctype.h>
函数原型说明 | 功能 | 返回值 |
---|---|---|
int isalnum(int ch) | 检查ch是否为字母或数字 | 是,返回1;否则返回0 |
int isalpha(int ch) | 检查ch是否为字母 | 是,返回1;否则返回0 |
int iscntrl(int ch) | 检查ch是否为控制字符 | 是,返回1;否则返回0 |
int isdigit(int ch) | 检查ch是否为数字 | 是,返回1;否则返回0 |
int isgraph(int ch) | 检查ch是否为ASCII码值在ox21到ox7e的可打印字符(即不包含空格字符) | 是,返回1;否则返回0 |
int islower(int ch) | 检查ch是否为小写字母 | 是,返回1;否则返回0 |
int isprint(int ch) | 检查ch是否为包含空格符在内的可打印字符 | 是,返回1;否则返回0 |
int ispunct(int ch) | 检查ch是否为除了空格、字母、数字之外的可打印字符 | 是,返回1;否则返回0 |
int isspace(int ch) | 检查ch是否为空格、制表或换行符 | 是,返回1;否则返回0 |
int isupper(int ch) | 检查ch是否为大写字母 | 是,返回1;否则返回0 |
int isxdigit(int ch) | 检查ch是否为16进制数 | 是,返回1;否则返回0 |
int tolower(int ch) | 把ch中的字母转换成小写字母 | 返回对应的小写字母 |
int toupper(int ch) | 把ch中的字母转换成大写字母 | 返回对应的大写字母 |
3、string.h 字符串处理函数库
函数原型说明 | 功能 | 返回值 |
---|---|---|
char *strcat(char *s1,char *s2) | 把字符串s2接到s1后面 | s1所指地址 |
char *strchr(char *s,int ch) | 在s所指字符串中,找出第一次出现字符ch的位置 | 返回找到的字符的地址,找不到返回NULL |
int strcmp(char *s1,char *s2) | 对s1和s2所指字符串进行比较 | s1<s2,返回负数;s1= =s2,返回0;s1>s2,返回正数 |
char *strcpy(char *s1,char *s2) | 把s2指向的串复制到s1指向的空间 | s1 所指地址 |
unsigned strlen(char *s) | 求字符串s的长度 | 返回串中字符(不计最后的’\0’)个数 |
char *strstr(char *s1,char *s2) | 在s1所指字符串中,找出字符串s2第一次出现的位置 | 返回找到的字符串的地址,找不到返回NULL |
4、math.h 数学库
函数原型说明 | 功能 | 返回值 | 说明 |
---|---|---|---|
int abs( int x) | 求整数x的绝对值 | 计算结果 | |
double fabs(double x) | 求双精度实数x的绝对值 | 计算结果 | |
double acos(double x) | 计算cos-1(x)的值 | 计算结果 | x在-1~1范围内 |
double asin(double x) | 计算sin-1(x)的值 | 计算结果 | x在-1~1范围内 |
double atan(double x) | 计算tan-1(x)的值 | 计算结果 | |
double atan2(double x) | 计算tan-1(x/y)的值 | 计算结果 | |
double cos(double x) | 计算cos(x)的值 | 计算结果 | x的单位为弧度 |
double cosh(double x) | 计算双曲余弦cosh(x)的值 | 计算结果 | |
double exp(double x) | 求ex的值 | 计算结果 | |
double fabs(double x) | 求双精度实数x的绝对值 | 计算结果 | |
double floor(double x) | 求不大于双精度实数x的最大整数 | ||
double fmod(double x,double y) | 求x/y整除后的双精度余数 | ||
double frexp(double val,int *exp) | 把双精度val分解尾数和以2为底的指数n,即val=x*2n,n存放在exp所指的变量中 | 返回位数x0.5≤x<1 | |
double log(double x) | 求㏑x | 计算结果 | x>0 |
double log10(double x) | 求log10x | 计算结果 | x>0 |
double modf(double val,double *ip) | 把双精度val分解成整数部分和小数部分,整数部分存放在ip所指的变量中 | 返回小数部分 | |
double pow(double x,double y) | 计算xy的值 | 计算结果 | |
double sin(double x) | 计算sin(x)的值 | 计算结果 | x的单位为弧度 |
double sinh(double x) | 计算x的双曲正弦函数sinh(x)的值 | 计算结果 | |
double sqrt(double x) | 计算x的开方 | 计算结果 | x≥0 |
double tan(double x) | 计算tan(x) | 计算结果 | |
double tanh(double x) | 计算x的双曲正切函数tanh(x)的值 | 计算结果 |
5、stdlib.h
字符串转换
double atof (const char*);
int atoi (const char*);
long atol (const char*);
double strtod (const char*, char**);
long strtol (const char*, char**, int);
unsigned long strtoul (const char*, char**, int);
随机数
内存管理
环境接口:
void abort (void);
void exit (int);
int atexit (void (*)(void));
int system (const char*);
char* getenv (const char*);
2.控制语句
1.顺序结构(我们之前打的代码都是顺序结构的)
2.分支结构(读者可以自寻一些题目来做或看书上的)
(1)if语句
主要是用来判断一些事务的
下列代码中,if(){}语句一定要有,其他都可有可无,视情况而定
#include <Stdio.h>
int main() {
if(条件){
//条件为真执行此条语句
//条件为假执行else if
}
else if (条件){
//条件为真执行此条语句
//条件为假执行下条else if
}
else if (条件){
//条件为真执行此条语句
//条件为假执行else
}
else{
//执行语句
//结束
}
}
下面我们用一个简单例子详细说明
#include <Stdio.h>
int main() {
int n;
scanf("%d",&n);
if(n){//这单独一个n是什么意思呢,其实等同与if(n!=0),也就是n不等于0,n不等于0的时候执行这if里面的代码
//不止是if语句可以这样,while,dowhile循环都可以这样,for循环这样用的比较少
printf("n不等于0~");
}
else if(n==1){//这里是个陷阱
//如果n等于1,那么n是不等于0的,那肯定就属于if里面的条件了,所以直接在if语句那就被截断了
//这个else if语句也就当然是执行不出来的
printf("n等于1");
}
else{
//上面if语句条件n不等于0,这里else你可以看成汉语的否则的意思,如果上面n不等于0,就执行if里面的代码,否则就执行else里面的代码
printf("优快云");
}
}
示例1:
输入:1
输出: n不等于0~
示例2:
输入:45
输出:n不等于0~
示例3:
输入:0
输出:优快云
(2) switch()语句
解析:
基础架构
#include <Stdio.h>
int main() {
int a;
scanf("%d",&a);
/*switch(表达式){ //表达式的值只能时整型,字符型和枚举型
case 常量表达式1: //常量表达式值的类型必须与表达式值的类型相同,如,a时int类型,常量的值也要时int类数据
语句1; //这里语句不止可以跟一条,还可以增添任意条语句在语句1之后
break; //break时退出当前循环,即退出switch语句.
//如果没有break语句,就会一直执行下去,比如下面去掉所有break,就会有a=1a=2a=3a=1这种结果.
case 常量表达式2:
语句2;
break;
case 常量表达式n:
语句n;
break;
default://default语句最多出现一次,位置最好是放在末尾,便于阅读
语句n+1;
}*/
switch(a){
case 1:
printf("a=1");
break;
case 2:
printf("a=2");
break;
case 3:
printf("a=3");
break;
default:
printf("a的值:%d",a);//如果a不是1,2,3就输出这条语句
}
}
3.循环结构(do …while , while,for )循环体内可以有若干条语句
(1)do…while循环
(先执行循环体部分一次,再进行判断是否满足下次循环的条件)
#include <Stdio.h>
int main() {
int a = 0;
int b = 10;
/*do{
循环体;
}while(条件);*/
do{
a++;
b--;
}while(a<10);
printf("%d %d",a,b);//结果:10 0
}
解析:先执行一遍,a++和b–,在判断a是否超过10,如果超过10则退出,执行printf语句.若没有超过则继续a++和b–,直到a超过10
(2)while循环
(先进行判断是否满足条件,如果满足条件,则执行循环体内容,直到不满足条件为止,如果刚开始就不满足条件,则直接跳过while循环)
#include <Stdio.h>
int main() {
int a = 0;
int b = 10;
/*do{
循环体;
}while(条件);*/
/*while(条件){
循环体;
}*/
while(a<5){
a++;
b--;
}
printf("%d %d",a,b);//结果:5 5
}
(3)for循环
(最好用于循环次数确定的情况)
#include <Stdio.h>
int main() {
int a = 0;
/*
do{
循环体;
}while(条件);
*/
/*
while(条件){
循环体;
}
*/
/*
for(表达式1;条件1;表达式2){
循环体;
}
*/
int count = 0;
for(int i = 1;i<=10;++i){
a++;
count++;
printf("第%d次a为%d\n",count,a);//从 第1次a为1 一直到 第10次a为10
}
}
注意! int i=1,这一句定义i,可以放在循环体外定义,如果要这样定于i,你就要用c99的特性,devc++中要在工具-编译选项-中点击编译时添加以下命令,加入 -std=c99即可
解析:
我们这里确定了循环次数,即循环10此,每次加1,所以可以用for循环,当然其他循环也是可以的,视情况而定.
i为1时满足条件i<=10,再执行循环体,循环体执行完之后,再执行++i,判断是否满足条件i<=10,满足继续循环体.
总结:1.先声明一个循环变量还构造一个循环
2.再就是条件->循环体->表达式2->条件->循环体->表达式2之间的循环了
3.i超出条件时就可以退出了
(4)关键字
(1)break;
break一般用于将整个循环退出,当然在switch中是个特例因为switch它不是一个循环.
但是如果是多个嵌套循环,就要考虑他在哪个循环里面,哪个循环外面
#include <Stdio.h>
int main() {
int count = 0;
for (int i = 1; i <= 5; ++i) {
for (int j = 1; j <= 5; ++j) {
printf("%d\n", count);
count++;
}
if (count == 10) {
printf("到10就退出了~");
break;
}
}
}
结果:
0
1
2
3
4
5
6
7
8
9
到10就退出了~
解析:即可以看到退出的是最外层的for循环,当count为10的时候就执行if判断语句了,此时break是退出最外层的for循环.
(2)continue;
continue一般用于循环跳出一次(即此次循环不执行,直接执行下一次的循环),并且在continue语句之后的语句都不会执行了
#include <Stdio.h>
int main() {
int count = 0;
for (int i = 1; i <= 15; ++i) {
count++;
if (count == 10) {
printf("碰到10了继续下一次for j循环~\n");
continue;
}
printf("%d\n", count);
}
}
结果:
1
2
3
4
5
6
7
8
9
碰到10了继续下一次for j循环~
11
12
13
14
15
解析:此时可以看到当count为10的时候,就会执行if语句的内容,并且不执行之后的语句了
3.函数
函数分为两大类:标准库函数和自定义函数
标准库函数即我们所说的stdio库中的输入输出函数,math库中的幂函数pow,sqrt,绝对值函数fabs等等
自定义函数则是由我们自己编写的一个函数,如一个求圆面积函数(computArea),写在main函数之外,只要在main函数里面接收这个函数返回的结果并输出即可,或者在computArea函数里面直接输出也是可以的.
(1)函数的定义与调用
下面介绍定义函数基础的语法
函数类型 函数名(形式参数表){
函数体
}
解析:函数有函数头和函数体两部分组成,函数头由函数类型,函数名和形式参数表(形参)组成.
函数类型是值函数返回值的类型,即指return后跟的也要是什么类型的数据,两者要相同,即如果是int类型的函数,return后跟的要是个整型数据.
如果函数没有返回值,那么它的类型就应该指定为void类型,即空类型.
函数名必须是一个有效的标识符,即一个能让人看的懂的,让别人知道什么作用的合规名字.这里的就是计算面积的意思
形式参数是用逗号分割的变量声明表,这些变量称为函数的形式参数,以后简称形参,形参用于就收传给这个函数的数据,如下列代码中,
double computeArea(double r){
}
r这个形参就相当于一个别名,它的数据是main函数中的radius传来的,目的是让这个radius的数据能在computeArea函数中延续下去.
下面让我们用一个计算圆面积的例子来做一个标准规范c
#include <stdio.h>
#include <math.h>
#define PI 3.14159
double computeArea(double r){
//函数类型 函数名(形式参数) ----函数头
//以下3段为函数体部分
double area;
area = PI * r * r;
return area;//返回的area的类型是double与函数类型相同
}
int main(){
double radius;
scanf("%lf",&radius);
if(radius >= 0){//进行判断
printf("圆面积:%.2f",computeArea(radius));//这括号里的radius就是实参 .2f限制只输出2为小数,默认输出6位小数
}
else{
printf("半径不合法");
}
}
示例1:
2
圆面积:12.57
示例2:
-1
半径不合法
解析:
1.首先,编译器会首先执行main函数,用于启动整个程序。函数其实是遵循着"先定义,后调用"的思想概念的,因为main函数要调用这个函数,就会从第 一行代码开始查找此函数(computeArea),所以要在main函数之前先定义(或者函数声明,下面会说)好了,如果computeArea函数放在main函数之后,main就找不到此函数 了。
2.执行到printf(“圆面积:%.2f”,computeArea(radius)); 这一行时,程序会去寻找->computeArea函数,找到了,由实参(radius)传入的值也会赋值给形参®,再 往下执行代码,当执行到 return area; 时,就会把算好的面积传给调用此函数的地方,即 printf(“圆面积:%.2f”,computeArea(radius)); 这一行代码, computeArea(radius)这个函数此时就成为了传回来的面积值.输出之后,程序结束。
(2)void函数说明:
如果一个函数没有返回值,那么这个函数的类型就为void,那么是什么时候要用到void函数呢,其实这要看你需要什么吧,比如上面的computeArea函数, 也可以是void类型的,即在main函数里面直接调用它,把实参传入,再在computeArea函数里面判断,再直接输出即可。
#include <stdio.h>
#include <math.h>
#define PI 3.14159
void computeArea(double r){//注意函数类型最好要是void,如果是其他类型虽然不会报错,但并不符合我们的编程习惯,如果没有返回值则一般都为void
//函数类型 函数名(形式参数) ----函数头
//以下3段为函数体部分
double area;
if(r >= 0){//进行判断
area = PI * r * r;
printf("圆面积:%.2f",area);//直接输出
}
else{
printf("半径不合法");
}
}
int main(){
double radius;
scanf("%lf",&radius);
computeArea(radius);//将实参传入,再调用函数
//如果函数类型为void,函数调用通常都当一条语句来写,如上面这条代码.
return 0;//表示程序结束,也可以不加
}
(3)谓词函数和函数声明说明:
有一类比较特殊的函数,其返回值为布尔类型(bool),我们编程习惯上,一般将谓词函数以is开头.
典型例子:输入一个回文数(即正读和反读都是一样的数,如515,262,7667等等),要求你调用一个函数判断是否为回文数,如果是则返回1,否则返回0.
如果弄不懂,拿笔按程序思路一算,就能出来,其实还有很多种其他方法,这里就不一一列举了.
#include <Stdio.h>
int isPalindrome(int num);//这里并没有函数体,只是一个函数声明
//函数声明就是告诉编译器,这里有一个xxx函数,如果编译器调用的就是这个函数,函数声明会把它引到函数定义的地方,即真正写了函数体的地方
int main() {
int n;
scanf("%d",&n);
if(isPalindrome(n)){//if语句说过,这样子这一条语句就等同于isPalindrome(int num)!=0,那么返回的就是1,就是回文数
printf("%d是回文数",n);
}
else{
printf("%d不是回文数",n);
}
}
int isPalindrome(int num){
int sameNum = num;//因为 num进行了取整操作,即每次去掉末尾那个数,如675,675/10 = 67
int isPnum = 0;//声明一个变量来判断输入的数字是否回文数,来进行判断
while(num!=0){//进行逆序操作:845逆序是548,646逆序是646,121逆序是121,所以看出回文数就是逆序之后和原数相同的数
isPnum = isPnum * 10 + num % 10;//每次取它个位数,再将他取出的个位数每次乘10,变成更高一位的数,再存到isPnum中
num/=10;
}
if(isPnum == sameNum){//拿逆序之后的数字与原数字进行判断,如果相同,那么是回文数
return 1;
}
else{
return 0;
}
}
(4)递归函数
递归的一个典型就是求一个数的阶乘,递归一般分为两个部分:递推和回归。
我们都知道,n!=n * (n-1)!,而(n-1)!又等于n-1 * (n-2)!,如此下去,但是一个阶乘总有到头的时候,即到了0!= 1这一步,这一系列的操作就是递推了,0!= 1我们称为基本情况,当递推碰到基本情况时,就开始回归了。
我们知道了0!= 1了,那就再回推算,通过0!算出1!( 1!= 1 * (1-1)!),再由1!算出2!,直到通过(n-2)!算出 (n-1)!,再由(n-1)!算出n!,这个过程就称为回归.
#include <Stdio.h>
double factorial(double num);//这里并没有函数体,只是一个函数声明
//函数声明就是告诉编译器,这里有一个xxx函数,如果编译器调用的就是这个函数,函数声明会把它引到函数定义的地方,即真正写了函数体的地方
int main() {
double n;
scanf("%lf",&n);
printf("%.0f的阶乘是:%.0f",n,factorial(n));
}
double factorial(double num){
if(num == 0){//当num为0时,即到了基本情况,0!= 1,返回即可
return 1;
}
else{
return num*factorial(num-1);//n*(n-1)!,n-1进入了factorial函数,当成了num,判断是不是到0了,没到0,就继续
}
}
解析:有可能会问,为什么要输入一个双精度浮点进去,阶乘不都是整数吗?对,但是因为程序精度问题,整型表达上限最大才21亿左右,而double能大很多,所以进行一个输出处理,看起来输出和整型没声明区别。
(5)变量的作用域
(1)局部变量和全局变量
局部变量:即在一对花括号内(复合语句)或函数内部声明的变量成为局部变量(也称内部变量)。
全局变量:默认值为0,在函数外部声明的变量,它可以在多个函数中使用,所以它的值可以被多个函数所更改,看情况使用。当一个函数运行完它不会马 上消失,会等到整个程序结束时才释放内存地址。
注意:函数中的形参也是有局部变量的性质
void computeArea(double r){//即此时的r也就是一个局部变量,应为r的作用域范围就是computeArea函数之内,在它的外面是用不了computeArea括号内r的值的.
}
下面我们通过例子来详细看到局部变量和全局变量的区别;
#include <stdio.h>
void fun1(void);//即空类型和空参数,说明不需要返回值,也不用参数传入,所以主函数只要调用它们就可以了,括号内的void可以省略
void fun2(void);
int main() {
fun1();
fun2();
}
//全局变量和局部变量使用位权:当一个函数中有这个变量的局部变量,则优先使用局部变量,如fun1,
//当一个函数内,没有找到此变量,则会在函数头之前找,看看有没有这个变量存在,如果有则访问它的内存地址。
int x = 3;//全局变量x,在主函数中你无法访问x,因为它定义在main函数之后
void fun1(void) {//fun为function(函数)的缩写,括号内的void可以省略,下同
int x = 1, y = 2;//局部变量,只要局部变量x,y出了这个函数,就释放了x和y的内存地址,就消失了。
printf("x = %d,y = %d\n", x, y);//结果:x = 1,y = 2
}
void fun2(void) {
int y = 4;
printf("x = %d,y = %d\n", x, y);//此函数未找到局部变量x,所以它会在函数头( void fun2(void) )找名为x的变量,所以用全局变量x = 3
//结果:x = 3,y = 4
}
下面我们清楚了一般的使用顺序,我们再加深点难度,看看改变局部变量和全局变量的值会不会有什么变化
#include <stdio.h>
void fun1(void);//即空类型和空参数,说明不需要返回值,也不用参数传入,所以主函数只要调用它们就可以了,括号内的void可以省略
void fun2(void);
int main() {
fun1();
fun2();
}
//全局变量和局部变量使用位权:当一个函数中有这个变量的局部变量,则优先使用局部变量,如fun1,
//当一个函数内,没有找到此变量,则会在函数头之前找,看看有没有这个变量存在,如果有则访问它的内存地址。
int x;//全局变量x,在主函数中你无法访问x,因为它定义在main函数之后
void fun1(void) {//fun为function(函数)的缩写,括号内的void可以省略
int y = 2;//局部变量,只要局部变量y运行到了这个函数末尾,即在这个函数内,后面没有代码了,就释放了y的内存地址,就消失了。
printf("x = %d,y = %d\n", x, y);//结果:x = 0,y = 2
++x;//等价于x = x+1; x = 0 + 1;
++y;//等价于y = y+1;
}
void fun2(void) {
int y = 4;
printf("x = %d,y = %d\n", x, y);//此函数未找到局部变量x,所以它会在函数头( void fun2(void) )找名为x的变量,所以用全局变量x = 3
//结果:x = 1,y = 4
}
解析:先调用fun1,printf语句时,找到了局部变量y,x未找到,所以向上找,找到了全局变量x,因为x未赋值,所以默认为0;
之后将x,y的值各加1,我们这里要清楚,这里的x是全局还是局部,很明显,x定义在外面,所以x是全局变量,如上所说,全局变量当一个函数运行 完它不会马上消失,会等到整个程序结束时才释放内存地址。所以x的值现在是1,而且是全局变量。
y是个局部变量,函数fun1运行完即释放内存,不会影响fun2中的局部变量y。所以在fun2中,编译器找到了全局变量x,值为1,局部变量y,值为4则 输出。这是fun2函数输出之后,变结束运行,注意这时候全局变量并没有释放,因为main函数还在运行,main函数之后也没有语句了,整个程序结 束,同时全局变量x释放内存。也就是说全局变量是跟main函数同存亡的,虽然main函数在这个程序里访问不到全局变量x。
(2)动态变量和静态变量
动态变量:默认值是未知的,不一定是0,当动态变量所属的函数和复合语句被执行时,该变量获得内存空间,函数或复合语句执行结束后立即释放该变量占据的内存空间,则该变量就不存 在了。
动态变量声明方式:这就是我们普通声明一个变量时的操作,如 int a = 5。
静态变量:默认值为0,静态变量占据固定的内存空间,只有整个程序执行结束后才会释放该变量占据的内存空间。(是否和之前的全局变量有相似之处)
静态变量声明方式:static 数据类型 标识符 = 值; ,如 static int a = 5;
下面我们通过示例来看看两者之间的区别
#include <stdio.h>
void fun1(void);//即空类型和空参数,说明不需要返回值,也不用参数传入,所以主函数只要调用它们就可以了,括号内的void可以省略
int main() {
fun1();//结果:2,2
fun1();//结果:3,2
}
void fun1(void) {
static int a = 1;//静态变量,只有整个程序执行结束后才会释放该变量占据的内存空间,也是与main函数同存亡的。
int b = 1;//动态变量,函数或复合语句执行结束后立即释放该变量占据的内存空间。
++a;
++b;
printf("a = %d,b= %d\n", a, b);/*执行这句后,a的值为2,b的值也为2,函数执行结束
a是静态变量,值还会保留着,而且main函数也没有结束,因为还有一句fun1();还没调用
b是动态变量,所以被释放了内存空间,此时b就仿佛从程序中消失了一般。
第二次调用,因为之前a的值是2,在+1即为3了,b的值是刚刚声明的为1,所以+1仍然为2
*/
}
(6)生成随机数
简介:其实,计算机中的随机数也是通过一些复杂算法推算出来的一个整数,所以这并不是我们理解中的随机数,只是一个伪随机数,同时它也是根据一个数,我们称它为种子放入公式得出来的,只要种子值相同,“随机”出来的数当然也是相同的.
(1)生成:使用stdlib.h头文件的rand()函数
(2)格式:int rand(void)
此函数返回一个0~RAND_MAX范围内(包括0和RAND_MAX)的整数。RAND_MAX是stdlib.h头文件中声明的常量。根据计算机和编译器的不同都有可能不同。
#include <Stdio.h>
#include <stdlib.h>
int main() {
int i;
printf("RAND_MAX = %d\n", RAND_MAX);
for (int i = 0; i < 5; ++i) {
printf("%d: %d\n", i, rand());
}
}
/*
结果:
RAND_MAX = 32767
0: 41
1: 18467
2: 6334
3: 26500
4: 19169
再执行一次
RAND_MAX = 32767
0: 41
1: 18467
2: 6334
3: 26500
4: 19169
*/
解析:实际上,rand()函数生成的伪随机数是由种子值产生的,即rand函数使用一个种子值控制随机数的生成,该”种子“的默认值为1。使用同一种子值就总会得到相同的随机数序列。
解决方法:我们此时只要改变种子值,这样产生的随机数序列也会发生改变,此时我们需要stdlib.h头文件中的srand(seed)函数来改变,传入的参数seed为unsigned int类型
#include <Stdio.h>
#include <stdlib.h>
int main() {
unsigned int seed;//无符号整数,取值范围是0,正整数。
printf("输入一个种子值:");
scanf("%u", &seed);//%u是无符号整型的格式符
srand(seed);
for (int i = 0; i < 5; ++i) {
printf("%d: %d\n", i, rand());
}
}
/*
结果:
输入一个种子值:2
0: 45
1: 29216
2: 24198
3: 17795
4: 29484
//第二次输入
输入一个种子值:3
0: 48
1: 7196
2: 9294
3: 9091
4: 7031
*/
由此可见,但是这样未免太麻烦了,所以我们需要一个东西来实时改变种子值,我们这是就可以调用time函数,它会返回时间。
同时,我们也要记住一个公式,用于生成a~b范围内的随机数,通常可以使用表达式a+rand()%(b-a+1)来表示,
例如,想生成1~100之间的随机数,1+rand()%(100)此表达式可以表达,
#include <Stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));//固定格式
int a, b;
int count = 0;
scanf("%d %d", &a, &b);//用于确定随机数的范围
while (count != 15){//用于输出15个随机数
printf("随机数:%d\n",a+rand()%(b-a+1));
count++;
}
}
/*
结果:
1 100
随机数:88
随机数:31
随机数:18
随机数:28
随机数:56
随机数:75
随机数:61
随机数:84
随机数:3
随机数:32
随机数:45
随机数:6
随机数:91
随机数:64
随机数:8
*/
4.数组
(1)什么是数组
数组是具有相同数据类型的一组相关变量的集合,其中每个变量都有相同的名字,但是有不同的下标,从0开始计.
(2)数组的声明和处理
=声明=
数据类型 数组名【常量表达式】
如
a = 10,,b = 5;
int a[2*a-b];
扩号内的一般是常量,但是在c99特性中,也可以使用非整型常量表达式,即括号里面的数字可以是由你输入的,但一定要是整型,这样会比较方便
如:
int a;
scanf("$d",&a);
int x[a*2];
这样你输入一个5,就会开辟一个x[10]出来,里面有很多个小容器,即x[0],x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8],x[8],x[9]这些存放数据的容器.
建议:在大多数情况下,为了便于我们调整数组长度,我们通常都是通过define来声明一个常量.
例如:
#include <Stdio.h>
#define SIZE 10
int main() {
int a[SIZE];
for (int i = 0; i < SIZE; ++i) {
scanf("%d", &a[i]);
}
for (int i = 0; i < SIZE; ++i) {
printf("%d ", a[i]);
}
}
结果:
11
22
3
44
55
66
77
88
99
100
11 22 3 44 55 66 77 88 99 100
解析:通过for循环让我们手动为每一位数组元素赋值,这时只要让i小于SIZE就好了,如果是个指定数值,还要记住,这样会比较方便,这样我们以后要修改数组长度,通过define来修改就好了,这只是我们的一种习惯,当然你想要哪种声明都是可以的.
=处理=
声明数组的时候想同时将它赋值,这中操作叫初始化,而且我们只能在一条语句中完成数组声明和初始化.
下列错做是错误的:
int a[2];//错误
a = {1,2};//错误
正确初始化是:
int a[2] = {1,2};
解析:这里将数组两个小容器都赋值了,a[0]为1.a[1]为2;
对于大数组赋值,这种方式未免太显得麻烦,而且不方便记忆.
int a[10] = {0,2,4,0,0,0,0,0,2,0};
对此,c99中的指定初始化可以解决这一问题,上面的例子可以这样写:
int a[10] = {[1] = 2,[2] = 4,[9] = 2};
这样可以明显感觉到方便了很多,其他没有赋值的下标都是默认为0,[]中的数字成为指示符,指示符必须是整型常量表达式,但对于c99来说可以稍加改变,指示符的范围从0开始,知道数组长度-1,如果数组长度省略,编译器将根据最大的指示符推断出数组长度(数组元素的个数)。
int a[] = {[1] = 2,[2] = 4,[11] = 2};
指示符的最大值是11,所以编译器推断数组长度是12.
长度运算符sizeof()
sizeof可以用来确定数组和数组元素的大小
int array[5] = {1,2,3,4};
int类的值占4字节,则数组大小sizeof(array)的值为16,数组元素大小sizeof(array[0])的值为4,则我们可以发现,用数组大小除以数组元素大小可以得出数组长度,即16/4 = 4(个),用表达式即 sizeof(array)/sizeof(array[0]) 的值为4
数组长度一般都是已知的,所以通常使用for循环来处理数组元素.
for循环处理数组元素赋值
#include <Stdio.h>
#define SIZE 10
int main() {
int a[SIZE];
for (int i = 0; i < SIZE; ++i) {
scanf("%d", &a[i]);
}
for (int i = 0; i < SIZE; ++i) {
printf("%d ", a[i]);
}
}
memset()函数处理数组赋值
memset函数必须包含string头文件
作用:为数组整体赋值,加入令array数组的元素均为1
memset(array,1,sizeof(array));
注意:两个相同类型的数组之间不同用赋值运算符来进行整体复制
如以下写法是错误的
secondArray = array;
必须逐个元素进行复制,并且要求目标数组(second)长度大于或等于源数组(secondArray)的长度
for(int i = 0;i < arraysize(数组的长度);++i){
secondArray[i] = array[i];
}
memcpy()函数处理数组复制
也是必须包含string头文件
memset(secondArray,array,sizeof(array);
(3)一维数组和函数
这一章基本是以示例题为主
(1).用户输入年份,打印该年每个月的天数。
注:2 月份以外,除了 1、3、5、7、8、10 和 12 月份有 31 天,其它月份均是 30 天;闰年的 2 月份是 29 天,非闰年是 28 天
提示:闰年判断条件,如果是闰年则能被4和100整除或者被400整除
#include <stdio.h>
int main(){
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int i, year;
printf("请输入一个年份:");
scanf("%d", &year);
if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)){//判断是否为闰年
days[1] = 29;
}
for (i = 0; i < sizeof(days) / sizeof(days[0]); i++){/*sizeof(days) / sizeof(days[0],之前我们讲过,这是长度运算符sizeof
可以快速获得数组元素个数,即数组长度,12,当然直接填写12当然也可以,只是在之后题目无法确 定数组长度的情况下可以这样用
*/
printf("%2d月份:%d天\n", i+1, days[i]);
}
return 0;
}
/*(2).写一个生活天数计算器,要求用户输入生日,显示他在这个世界上活了多少天?
同时也为大家介绍一个新的语句---goto语句
什么是goto语句?
goto语句也称为无条件转移语句,其一般格式如下: goto 语句标号;
其中语句标号是按标识符规定书写的符号, 放在某一语句行的前面,标号后加冒号(:)。
语句标号起标识语句的作用,与goto 语句配合使用。
功能:goto语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。
使用建议:但是,在结构化程序设计中一般不主张使用goto语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难,所以能不用,尽量不用*/
#include <stdio.h>
int main(){
long count = 0; // count用于存放一共活了多少天
int year1, year2; // year1是你的生日年份,year2是今天的年份
int month1, month2;
int day1, day2;
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
printf("请输入你的生日(如1988-05-20):");
scanf("%d-%d-%d", &year1, &month1, &day1);//输入的时候也是加‘-’号
printf("请输入今年日期(如2016-03-28):");
scanf("%d-%d-%d", &year2, &month2, &day2);
while (year1 <= year2){
days[1] = (year1 % 400 == 0 || (year1 % 4 == 0 && year1 % 100 != 0)) ? 29 : 28;
while (month1 <= 12){
while (day1 <= days[month1 - 1]){
if (year1 == year2 && month1 == month2 && day1 == day2){
goto FINISH; // 跳出多层循环才被迫用goto语句
}
day1++;
count++;
}
day1 = 1;
month1++;
}
month1 = 1;
year1++;
}
FINISH: printf("你在这个世界上总共生存了%d天\n", count);
return 0;
}
(4)二维数组
一维数组对我们来说就只有长度,而没有宽度,在几何中也这样认为,即线。而二维数组就是不止有长度,而且还有宽度。
(1)声明和处理二维数组
=声明=
//数据类型 数组名 [常量表达式1] [常量表达式2];
//解析:常量表达式1表示的是一个行的长度,通俗的说就是有几行
//常量表达式2表示的是列的长度,通俗的说则是有几列
//下标:同一维数组一样,都是以0开头
// 如:int a[3] [2];
//我用平面展开更好理解
/*
a[0][0] a[0][1]
a[1][0] a[1][1]
a[2][0] a[2][1]
*/
=处理=
同样,我们也可以为它初始化,即声明的同时为它赋值,这里就是使用嵌套的数组初始化式给数组元素赋初始值.
int a[3][2] = {{1,2},{3,4},{5,6}};
/*
a[0][0]=1 a[0][1]=2
a[1][0]=3 a[1][1]=4
a[2][0]=5 a[2][1]=6
*/
当然,我们也可以不用中间那几对花括号,外面花括号套下去就可以了
int a[3][2] = {1,2,3,4,5,6};//与上方代码等价,推荐使用上方,虽然有些麻烦,但更为直观一些
还有一些其他省略了行长度的写法,能看懂时什么意思即可
//注意!列长度不能省略
int a[][2] = {1,2,3,4,5,6};
//为什么能省略行长度呢?
//编译器能利用数组初始化式子的初始值个数6以及列长度2,来确定数组行长度为3,即6/2=3
遍历二维数组
#define ROW 3//声明最大行数
#define COL 2//声明最大列数
#include <Stdio.h>
int main() {
int array[ROW][COL];//3行2列的数组
printf("======输入开始=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL; ++j) {
scanf("%d", &array[i][j]); //遍历输入
}
}
//遍历输出
printf("======输出开始=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL-1; ++j) {
printf("%d ", array[i][j]); //遍历输出除最后一列之外的元素,因为我们要在它末尾换行,达到二维数组的平面展示效果
}
printf("%d\n" ,array[i][COL-1]);//根据i输出每行最后一列的元素
}
}
/*
结果:
3 5
4 8
9 6
======输出开始=====
3 5
4 8
9 6
*/
memset函数为二维数组整体赋值
必须包含string头文件
#define ROW 3//声明最大行数
#define COL 2//声明最大列数
#include <Stdio.h>
#include <string.h>
int main() {
int array[ROW][COL];//3行2列的数组
遍历输出
printf("======输出开始=====\n");
memset(array, 0, sizeof(array));//将每位数组元素均赋值为0
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL - 1; ++j) {
printf("%d ", array[i][j]); //遍历输出除最后一列之外的元素,因为我们要在它末尾换行,达到二维数组的平面展示效果
}
printf("%d\n", array[i][COL - 1]);//根据i输出每行最后一列的元素
}
}
/*
结果:
======输出开始=====
0 0
0 0
0 0
*/
复制二维数组
同一维数组相同,不能用两个二维数组名相互直接复制
int secondArray = array;//错误
方法1 遍历复制
逐个元素进行复制,且要求目标数组(secondArray)的行长度和列长度都要大于或等于源数组(array)的行长度和列长度.
#define ROW 3//声明最大行数
#define COL 2//声明最大列数
#include <Stdio.h>
#include <string.h>
int main() {
int array[ROW][COL];//3行2列的数组
int secondArray[ROW][COL];//声明一个和array相同长度和列宽的数组
//遍历为数组array输入
printf("======输入开始=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL; ++j) {
scanf("%d", &array[i][j]); //遍历输入
}
}
//遍历复制
printf("======复制开始=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL; ++j) {
secondArray[i][j] = array[i][j];
}
}
//遍历为secondArray输出
printf("=====为secondArray数组遍历输出=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL - 1; ++j) {
printf("%d ", secondArray[i][j]); //遍历输出除最后一列之外的元素,因为我们要在它末尾换行,达到二维数组的平面展示效果
}
printf("%d\n", secondArray[i][COL - 1]);//根据i输出每行最后一列的元素
}
}
/*
结果:
======输入开始=====
1 2 5 6 7 8 //对二维数组有较好的理解之后,我们就可以这种一行输入了
======复制开始===== //代表执行了复制操作
=====为secondArray数组遍历输出=====
1 2
5 6
7 8
*/
方法2 memcpy()
memcpy(目标数组名,源数组名,sizeof(源数组名));//必须包含string.h头文件
#define ROW 3//声明最大行数
#define COL 2//声明最大列数
#include <Stdio.h>
#include <string.h>
int main() {
int array[ROW][COL];//3行2列的数组
int secondArray[ROW][COL];//声明一个和array相同长度和列宽的数组
//遍历为数组array输入
printf("======输入开始=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL; ++j) {
scanf("%d", &array[i][j]); //遍历输入
}
}
printf("======复制开始=====\n");
/*
//遍历复制
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL; ++j) {
secondArray[i][j] = array[i][j];
}
}
*/
memcpy(secondArray, array, sizeof(array));
//遍历为secondArray输出
printf("=====为secondArray数组遍历输出=====\n");
for (int i = 0; i < ROW; ++i) {
for (int j = 0; j < COL - 1; ++j) {
printf("%d ", secondArray[i][j]); //遍历输出除最后一列之外的元素,因为我们要在它末尾换行,达到二维数组的平面展示效果
}
printf("%d\n", secondArray[i][COL - 1]);//根据i输出每行最后一列的元素
}
}
/*
结果:
======输入开始=====
5 6 2 2 6 9
======复制开始=====
=====为secondArray数组遍历输出=====
5 6
2 2
6 9
*/
(5)排序和查找(了解)
目前由很多排序算法,由冒泡排序,选择排序,和插入排序这三种为基础的排序算法。
(1)冒泡排序
#include <stdio.h>
#define SIZE 7
int main() {
int a[SIZE] = { 1,6,5,8,9,4,3 };//我们想要从小到大排序
for (int i = 1; i < SIZE; ++i) {
for (int j = 0; j < SIZE - i; ++j) {//每排完一次,就减少排序次数1次
if (a[j] > a[j + 1]) {//每次与后一个元素进行排序,如果当前元素大于后一个元素则互换值,当前值变的比后一个元素小
//如果想从大到小,就只要将大于号改为小于号就可以了
int t;//定义一个交换变量进行交替赋值
t = a[j + 1];
a[j + 1] = a[j];
a[j] = t;
}
}
}
for (int i = 0; i < SIZE; i++){
printf("%d ", a[i]);//结果1 3 4 5 6 8 9
}
}
(2)选择排序
排序思想:选择排序是指第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾,依次类推,直至排完所有数据即没有待排序的数据元素了
我们想将元素从小到大排序
#include<stdio.h>
int main() {
int i, j, temp, array[11];//temp定义为交换变量
printf("请输入10个数:\n");
for (i = 1; i <= 10; i++) {
scanf("%d", &array[i]);
}
for (i = 1; i <= 9; i++) {
for (j = i + 1; j <= 10; j++) {
if (array[i] > array[j]) { //如果前一个数比后一个数大,则利用中间变量t实现两值互换
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
printf("排序后:\n");
for (i = 1; i <= 10; i++){
printf("%2d", array[i]);
}
}
/*结果:
请输入10个数:
1 2 5 6 8 9 1 3 5 6
排序后:
1 1 2 3 5 5 6 6 8 9
*/
(3)插入排序
我们就不再叙述了,前两个基本已经够用,有意者可自行去优快云上查询,我们理下排序思想
排序思想:对于未排序数据,在已排序数据中从后向前扫描,找到相应的位置并插入。在从后向前的扫描过程中,需要反复把已排序数据逐步向后挪位,为最新数据提供插入空间。
(4)顺序查找
顺序查找就是将需要查找的元素顺序与数组中的每一个元素进行对比。所以由两种结果,要么找到某一个元素与我们向查找的元素匹对,要么找完了都没有找到相同的元素.
(5)二分查找
二分查找又称折半查找,优点是对于大数组的查找效率极高,缺点是数组必须是要有序的(按升序或降序存放)
现在我们假设数组中的元素按升序存放,将关键字与数组的中间元素进行比较,比较有3种情况
(1)如果关键字小于中间元素,则我们只要在数组的前半部分比较即可,同时我们再在前半部分数组中确认一个中间元素,再判断关键字和中间元素的关系,是大于小于还是等于,一直算到关键字等于中间元素为止。
(2)如果关键字等于中间元素,则输出中间元素即可,查找结束
(3)如果关键字大于中间元素,那我们就在数组的后半部分比较,同时我们在后半部分数组中确认一个中间元素,再判断关键字和中间元素的关系,是大于小于还是等于,一直算到关键字等于中间元素为止。
下面是我们的查找函数,我们只要传入数组名,数组大小,关键字即可,再接收返回的下标即可。
int searchInsert(int* nums, int numsSize, int target){//target是关键字,nums是数组,numsSize是数组大小
int mid,low = 0;//mid是我们说的中间变量,low即首元素的下标,high为末尾元素的下标,用来算mid
int high = numsSize-1;
while(low <= high){ //判断循环终止的条件
mid = (high + low) / 2;//取最中间的数
if(target == nums[mid]){//如果目标数字和最中间的数相等,直接返回
return mid;
}
else if(target > nums[mid]){//如果目标数字大于中间的数,把左边的low向右移到mid+1
low = mid + 1;
}
else if(target < nums[mid]){//如果目标数字大于中间的数,把右边的high向右移到mid-1
high = mid - 1;
}
}
return low;
}
5.指针
(1)基本概念
首先我们要了解指针,就要清楚声明是内存地址值,我们都知道变量由四个部分组成,变量名、数据类型、内存空间地址和变量值。
变量名和数据类型在我们编写程序时就需要确定
变量值当然也可以同时确定,也可以在之后输入确定
而内存空间地址我们之前讲的很少,我们这次详细讲解
内存空间地址:变量所占用的内存空间地址在程序编译时由编译器确定。内存空间一般以字节为单位,每个字节可以存储8位信息,每个字节都有唯一的地址。程序中的每个变量都会占用一个或多个字节的内存空间,把变量所占用的内存空间的第一个字节地址成为变量地址。
例如:
int n = 10;
这一步操作,编译器接收到了一个新的变量,变量名是x,变量中存放的是一个int型整数,并为它分配对应的内存空间,将初始值10放在该内存空间。但是一般情况下,我们一般不关心内存所分配的内存地址值,只能通过变量名n访问其所占用的内存空间。
内存地址以字节为单位,通常用十六进制数来表示。
虽然内存地址是整数,但是内存地址的取值范围可能不同于整数的取值范围,所以不能用整型变量来存储内存地址,必须使用特殊的指针变量存储内存地址。
所以C语言就引用了一种特殊的数据类型——指针,用来表示内存地址,即指针中存放的是一个的内存地址,而指针变量是存放内存地址的变量。
指针包含两个信息:内存地址值和所指向的变量的类型。
(2)声明指针变量
数据类型* 指针变量名
其实,指针也有它的地址值即 &指针变量名 此时这时要用%p格式符来输出,表示输出一个内存地址,之后会介绍。
例如:
int* p;//我们通常用p变量来表示指针变量
/*
同时我们也有另外两种方式来声明指针变量
int *p;
int * p;
我们推荐用第一种,因为之后要通过指针进行一系列操作,这样会比较清楚,不会混淆,当然喜欢用哪种都可以
*/
//=========================================分割线
int* p = NULL;//表示p为指针,暂不指向任何整型变量,NULL可以赋值给任何类型的指针
//=========================================分割线
//分别定义了 int、float、char 类型的指针变量
int* x;
float* f;
char* ch;
//如上面的定义,指针变量名为 x、f、ch。并不是*x、*f、*ch
(3)取地址运算符和解引用运算符
为了使用指针,C语言专门提供了一对运算符:取地址运算符”&“和解引用运算符"*",如果x是变量,则&x就是变量x的内存地址。如果p是指针,则 *p就是p当前所指向的变量的值。
//1.空指针NULL
int* p = NULL;//表示p为指针,暂不指向任何整型变量,NULL可以赋值给任何类型的指针
//=========================================分割线
//2.同类型对象的指针
int* p,n;
p = &n;
//也可以这样写
int n;
int* p = &n;//将n的内存地址赋值给指针变量p
//=========================================分割线
//3.同类型的指针
int* p1,*p2,n;
p1 = &n;
p2 = p1;//这时候p2指向一个内存地址值,就是n的内存地址值,因为p1指向n,说明p1存储的是n的地址值,也就是说p1的值是n的地址值。
//此时p1的值被赋值给了p2,所以p2中也存储着n的地址值,所以也指向了n
下面我们通过示例来看看指针变量和变量之间的操作
例1:看清变量的地址值 和 指针变量所指向的变量的地址值 和 指针变量地址值
#include<stdio.h>
int main() {
int* p1, * p2, n;
p1 = &n;
//%p是取地址占位符,说明要传入一个地址值,所以如果是变量要&n,代表取出n的内存地址值,如果是指针,只要直接输出指针名即可,因为指针所保存的值就是内存地址值
printf("%p\n", &n);//输出n的地址值 001BFE7C
printf("%p\n", p1);//输出p1的值 001BFE7C,值就是n的地址值,我们口头说就是p1指向n,就代表了赋值行为
printf("%p\n", &p1);//输出p1的地址值 00BEF840
}
例2:熟悉解引用运算符的使用
这时,这也是我为什么推荐为什么声明指针用第一种方式,因为声明指针如果是int *p;会和解引用 *p混淆,为了避免混淆,最好使用第一种.
#include<stdio.h>
int main() {
int* p1, n = 100;
p1 = &n;
printf("n的值:%d\n",n);//100
printf("n的地址值:%p\n", &n);//输出n的地址值 001BFE7C
printf("p1的值:%p\n", p1);//输出p1的值 001BFE7C,值就是n的地址值,我们口头说就是p1指向n,就代表了赋值行为
printf("p1的地址值是:%p\n", &p1);//输出p1的地址值 00BEF840
printf("p1所指向的内存空间中所存放的值:%d\n",*p1);//100, 此时*p1就等价于n,步骤就是通过内存地址值来寻找所指向的变量的值
printf("变量n的值%d",*&n);//无特别意思,只是说明先取它的地址,编译器再通过内存地址来寻找变量的值,就等于它本身了
}
(4)通用指针
相同类型之间的指针变量可以相互赋值,不同类型的指针变量不可以直接赋值,也需要强制类型转换。
int* p;
double* p2;
p = p2;//错误写法,不能直接赋值
p = (int*)p2;//正确,强制类型转换后进行赋值,当然p2赋值给p之前也要指向一个double类型变量,不然如果直接输出在vs中会报错,
(1)void指针
我们前面提到,一个指针应该包含以下两个信息:变量的内存地址值和所指向的变量的类型。但也是由特例存在,即指针只包含内存地址值而不包含所指向的变量的类型,这种指针就是指向void类型的指针,即void类型,也成为通用指针。
void指针:它可以指向任何类型的变量,也就是void指针可以取任何类型的变量的内存地址值。
例如:
#include <stdio.h>
int main() {
int x = 100;
int* p = &x;
void* v;//声明空指针 v
v = p; //直接将整型指针赋值给空指针,也就是p所指向变量的地址值赋值给了空指针,也就是这时候空指针有了x的地址值
printf("x的地址值:%p\n", &x);
printf("整型指针p所保存的地址值:%p\n", p);
printf("void空指针所保存的地址值:%p\n", v);
v = &x;
printf("void空指针所保存的地址值:%p\n", v);///和上面一样,指向指针和指向变量它的内存地址值不会改变,因为指向的都是x变量
/*
结果:
x的地址值:012FFE34
整型指针p所保存的地址值:012FFE34
void空指针所保存的地址值:012FFE34
void空指针所保存的地址值:012FFE34
解释:我们这是可以发现,void是可以存放内存地址值的,注意每次运行的地址值都可能不同,因为内存是实时分配的,运行一次,
编译器就给你分配一个内存地址值
*/
}
我们通过上一个例子知道了空指针它是可以保存内存地址值的,也可以被其他指针赋值,但是void指针可以赋值给其他指针吗?
答案是不行的,因为我们都只知道void是空类型,两个变量完整赋值,肯定要类型一样,所以我们要将空指针赋值给其他指针,也要强制类型转换.
#include <stdio.h>
int main() {
int x = 100;
int* p;//声明一个整型指针,暂不指向什么变量,因为我们想用void空指针来赋值
void* v;//声明空指针v
v = &x;
//p = v;//错误:不能直接赋值,类型不同,无法从“void *”转换为“int *”
p = (int*)v;//正确:通过强制类型转换赋值
printf("x的值:%d,x的地址值: %p\n",x,&x);
printf("p解引用后:%d,p的地址值:%p\n", *p, p);
printf("空类型指针的解引用值: %d,空类型指针的地址值: %p\n", *(int* )v,(int* )v);//前一个解释:先强转为整型指针,再解引用输出
/*
结果:
x的值:100,x的地址值: 00CFF988
p解引用后:100,p的地址值:00CFF988
空类型指针的解引用值: 100,空类型指针的地址值: 00CFF988
解析:成功通过强制类型转换来进行赋值
*/
}
(2)const修饰指针
简介:我们知道,const对变量来说是指定为一个常量,即const double PI = 3.141592,程序只能读取PI的值,不能修改,同时声明常量的时候必须赋一个初始值,不然就是错误的。回归整体,那么const对于指针来说,意味着什么呢,其实也一样,但是有三种情况
1.指针所指向的变量的值为常量(即指针所指向的变量的值不能被改变-----const在指针运算符(*)的左边
2.指针本身的值不能被改变(即指针不能改变所指向的变量-----const在指针运算符(*)的右边
3.指针所指向的变量的值为常量并且指针本身的值不能被改变-----const在指针运算符(*)的两边
(1)const在指针运算符(*)的左边 即指针所指向的变量的值不能被改变
#include <stdio.h>
int main() {
int x = 100, y = 200;
const int* p = &x;
/*
const在*号的左边,表示const此时修饰的是指针所指向的变量,所以指针所指向的值不能修改,
而指针本身的值可以修改也就是说指针可以指向其他变量如y
*/
//*p = 88; //错误 尝试修改x的值(*p就等价于x),因为此时*p即x是一个常量
p = &y;//正确 可以让p指向新的变量y
printf("%d", *p);//结果:200
}
(2)const在指针运算符(*)的右边 即即指针不能改变所指向的变量
#include <stdio.h>
int main() {
int x = 100, y = 200;
//const int* p = &x;//const在*的左边
int* const p = &x;//const在*的右边
/*
const在*号的右边,表示const此时修饰的是指针本身,所以指针本身不能修改,
而指针所指向的的值可以修改也就是说指针所指向的变量的值能被改变
*/
*p = 88; //正确 尝试修改x的值(*p就等价于x)
//p = &y;//错误 可以让p指向新的变量y,这时不能再指向任何变量,只能指向它本身即x
printf("%d", *p);//结果:88
}
3.const在指针运算符(*)的两边 即指针所指向的变量的值为常量并且指针本身的值不能被改变
#include <stdio.h>
int main() {
int x = 100, y = 200;
//const int* p = &x;//const在*的左边
//int* const p = &x;//const在*的右边
const int* const p = &x; //const在指针运算符(*)的两边
/*
const在*号的两边,表示const此时修饰的是指针本身还有指针所指向的变量 所以指针本身不能修改,指针所指向的变量的值也不能修改
而指针所指向的的值可以修改也就是说指针所指向的变量的值能被改变
*/
//*p = 88; //错误
//p = &y;//错误
printf("%d", *p);//结果:100
}
(5)指针和函数
大体可以将此分为四部分–值传递、引用传递、函数指针、指针函数
1值传递
值传递即函数参数列表中的形参是局部变量
下面通过交换变量来详细说明
#include <stdio.h>
void swap(int x, int y);//函数声明
//目的:我们想调换num1和num2之间的值,但是要通过另一个函数(swap)来完成
//注释:虽然更麻烦,但是这只是为了更好的让我们理解什么是值传递和引用传递的区别
int main() {
int num1, num2;
scanf("%d%d", &num1, &num2);
printf("交换前:%d %d\n", num1, num2);
printf("进行交换\n");
swap(num1, num2);
printf("交换完毕\n");
printf("交换后:%d %d\n", num1, num2);
/*
结果:
3 5
交换前:3 5
进行交换
交换完毕
交换后:3 5
*/
/*
解析:为什么没有交换成功呢?
我们知道,函数中的形参是一个局部变量吧,一个局部变量它的作用域是不是只有在本函数内,这也就解释通了,当在swap函数内,他们的值确实是交换了,
但是出了swap函数之后,这个交换后的值就消失了,就是被内存释放了内存空间。所以交换后的值当然还是他们本身了
总结:实参变量的值传递给形参,无论形参的值如何改变,实参都是不会收到影响,因为一出了函数,形参的值就消失了,被内存释放了
*/
}
void swap(int x, int y) {
int t;
t = x;
x = y;
y = t;
}
2引用传递
引用传递即函数参数列表中的形参是存放局部变量的地址值的同类型指针
#include <stdio.h>
void swap(int* x, int* y);//函数声明,形参是存放局部变量的地址值的同类型指针
//目的:我们想调换num1和num2之间的值,但是要通过另一个函数(swap)来完成
//注释:虽然更麻烦,但是这只是为了更好的让我们理解什么是值传递和引用传递的区别
int main() {
int num1, num2;
scanf("%d%d", &num1, &num2);
printf("交换前:%d %d\n", num1, num2);
printf("进行交换\n");
swap(&num1, &num2);//传入两个变量的地址值
printf("交换完毕\n");
printf("交换后:%d %d\n", num1, num2);
/*
结果:
3 5
交换前:3 5
进行交换
交换完毕
交换后:5 3
*/
/*
解释:有可能会问,为什么这次局部变量结束了,指针的值不会消失呢?
其实,指针确实消失了,但是在消失前完成了值的转换,在上个值传递中,swap里的形参是一个局部变量,是个新的值,只是被赋值了而已
而他们的内存地址值是不同的。而指针中存储的就是num1和num2的地址值,所以*x和*y代表的是真正的num1和num2,他们内存地址值相同
所以*x和*y改变,实参也会跟着改变。
*/
/*
总结:调用一个带指针参数的函数时,实参变量的地址值传递给指针形参,所以形参和实参共享相同的变量。
*/
}
void swap(int* x, int* y) {//接收的是两个变量的地址值,
int t;
t = *x;
*x = *y;
*y = t;
}
提前说明:指针函数和函数指针的概念经常会混淆,前一个是返回指针的函数,后一个是指向函数的指针
3指针函数
格式:函数类型 *函数名(形式参数表);
函数的返回值是指针时,称之为指针型函数,通常用来获取指针所指向的对象的值
#include <stdio.h>
int *max(int* x, int* y);//函数声明,形参是存放局部变量的地址值的同类型指针
//目的:我们想要获得一个最大值,通过max指针函数来完成
int main() {
int num1, num2;
scanf("%d%d", &num1, &num2);
int* result = max(&num1, &num2);//传入两个变量的地址值,返回的也是一个指针,返回后由一个整型指针接收一个最大值变量的地址值
printf("最大值:%d\n", *result);
/*
结果:
3 5
最大值:5
*/
}
int* max(int* x, int* y) {
if (*x > *y) {
return x;//返回x的值即所指向的变量的地址值
}
else{
return y;
}
}
4函数指针
格式:函数类型 (*函数名)(形式参数表);
注意 * 和函数名要用括号括起来,否则因为运算符的优先级原因就变成指针函数了,运算符的优先级比 * 指针运算符高
函数名外层的括号让函数名先与 * 结合,表示是一个指针,再与后面的()结合,表明指针指向的的是一个函数.
#include<stdio.h>
int sum(int x, int y);
int (*fun) (int, int); //声明函数指针时,形式参数表中的形参名可以省略
int main(){
fun = sum; //fun函数指针指向add函数,fun这个函数指针中就有了sum函数的地址值
printf("%d\n", fun(3, 5)); //这上下两种都可以,推荐使用此种,fum(3,5)此时等价于sum(3,5)
printf("%d", (*fun)(4, 2));
/*
结果:
8
6
*/
}
int sum(int x, int y){
return x + y;
}
(6)指针与数组
在C语言里,数组与指针的关系时十分密切的,我们之前讲过,数组名代表的就是一个当前数组的首元素的地址值,也就是说这就是一个常量指针(即const在*的右边的指针,不能改变所指向的变量),所以数组名不能直接与令一个数组(相当于令一个常量指针)进行复制操作,所以其数组首元素地址就是一个常量。
那么例如
#include<stdio.h>
int main(){
int a[] = { 1,2,3,5,4 };
printf("数组名代表数组首元素的地址:%p\n", a);
for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++){
printf("地址:%p,值:%d\n", a+i, *(a + i));
//第二个元素*(a + i),代表每次向后遍历1位,a+1就代表数组第二位元素的地址值,而加上解引用符就代表数组第二位元素的值,
}
/*
结果:
数组名代表数组首元素的地址:00F3FD4C
地址:00F3FD4C,值:1
地址:00F3FD50,值:2
地址:00F3FD54,值:3
地址:00F3FD58,值:5
地址:00F3FD5C,值:4
*/
/*
解析:这是一个整型数组,所以4个字节代表一个元素,4C+4十六进制是满16进1,即50.50再加4即54,以此类推
*/
}
1.指针指向数组
#include<stdio.h>
int main(){
int a[] = { 1,2,3,5,4 };
int* p = a;//此时p指向a数组中首元素的地址
printf("数组名代表数组首元素的地址:%p\n", a);
for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++){
printf("地址:%p,值:%d\n", a+i, *(a + i));
printf("p指针:地址:%p,值:%d\n", p + i, *(p + i));
printf("甚至可以p[i]= %d\n", p[i]);
//第二个元素*(a + i),代表每次向后遍历1位,a+1就代表数组第二位元素的地址值,而加上解引用符就代表数组第二位元素的值
}
/*
结果:
数组名代表数组首元素的地址:005CF98C
地址:005CF98C,值:1
p指针:地址:005CF98C,值:1
甚至可以p[i]= 1
地址:005CF990,值:2
p指针:地址:005CF990,值:2
甚至可以p[i]= 2
地址:005CF994,值:3
p指针:地址:005CF994,值:3
甚至可以p[i]= 3
地址:005CF998,值:5
p指针:地址:005CF998,值:5
甚至可以p[i]= 5
地址:005CF99C,值:4
p指针:地址:005CF99C,值:4
甚至可以p[i]= 4
*/
/*
解析:p指针指向了数组的时候,就说明你可以把p也当作另一个数组名来看待了
*/
}
2.指针数组
1指针与一维数组
简介:就是指针数组中的每一位元素都是存放内存空间的,也就是一个指针,每一位元素都是指针
格式:数据类型 *数组名[常量表达式];
解释:数组名首先与后面的[]结合(下标运算符[]的优先级为1,之前的优先级表中有),表明是数组,再与前面的 * 指针运算符结合,说明数据元素类型是指针类 型
#include<stdio.h>
int main(){
int a = 1, b = 2, c = 3;
int* p[3];
p[0] = &a;
p[1] = &b;
p[2] = &c;
//数组p是一个有3个元素的指针数组,每个数组元素都指向一个整型变量
}
2指针与二维数组
在二维数组中,直接用数组名,代表的是行的首元素同时也是列的首元素,就是第一行第一列的第一个元素的地址值,我们用平面展开方式来看看数组名+n代表什么含义
/*
int a[3][2];//三行两列
a----->a[0]----->a[0][0] a[0][1]
a+1--->a[1]----->a[1][0] a[1][1]
a+2--->a[2]----->a[2][0] a[2][1]
也就是说数组名+n代表的是第n行的首元素的地址值,即a+n 等价 a[n] 等价 &a[n][0]
*/
那么向让指针p访问数组a中第n行的元素,可以让p指向数组a中第i的首元素
p = &a[n][0];
//由上可得等价于 p = a[n];
想通过p指针来使数组a中第n行的元素全部为0
#include<stdio.h>
int main(){
int* p, n;
scanf("%d", &n);
int a[3][2] = { 1,3,4,5,2,6 };
for ( p = a[n]; p <= a[n]+1; p++){
*p = 0;//p = &a[n][0],p++后,p = &a[n][1]
}
}
(7)动态存储分配
静态存储方式:是指在程序运行期间由系统分配固定的存储空间的方式。
动态存储分配:是指在程序运行期间根据需要进行动态的分配存储空间的方式,动态存储分配的内存空间通常称为堆。
我们常用的动态分配内存空间,C语言中主要有malloc()、colloc()函数、free()和realloce()函数,当内存满时是由可能分配失败的
其中free函数就是释放分配的内存空间用的
1.malloc()函数
格式:void* malloc(size)
作用:malloc函数分配指定大小(size个字节)的内存空间,但是不会对分配的内存空间进行初始化,并返回指向该内存空间的通用指针。
int* p;
p = (int *)malloc(100*sizeof(int));//分配了可以存放100个整数的内存空间,有可能有人会问sizeof(int)不就是4吗?你要分配整数,写4不就好了吗
//其实在我们编程习惯中,我们一般都习惯这样写,因为这样更明了你要分配一段整数空间
//注意:在执行赋值操作的时候,会把malloc()函数返回的通用指针 void* 自动转换为 int* ,但是在编程习惯中,也最好明确下,用强制类型转换,来明了你要分配的是什么类型的内存空间。
2.free()函数
格式:void free(void* p)
作用:释放之前由malloc或calloc函数分配的内存空间,指针p指向要释放的内存空间,即你要释放哪个指针所分配的内存空间,你就填这个指针进去即可
//如上例malloc所分配的p指针
free(p);//即释放完成
3.colloc()函数
格式:void* colloc(int 所分配的个数,sizeof(数据类型或数组名));
int* p;
p = (int *)colloc(10,sizeof(int));//分配10个整型变量的空间
那么这些内存空间分配出来有什么用呢?
我们分配动态内存地址空间主要是应对数组中的内存地址可能有些用不到,存在浪费,所以只要输入你想要的大小,然后malloc或calloc一下,这样那个指针就可以当做一个数组来做,我们之前说过指针和数组的关系很密切。
(6)字符串
(1)基本概念
程序在之后都是需要处理字符序列的,可以用字符数组来存储字符序列。
而字符数组就是其元素类型为字符型的数组,每个元素存放一个字符。
例如:char ch[5];
当然了,也可以为它进行初始化
char ch[5] = {'a','B','1'};
//声明字符数组的时候,如果这时给定了数组初始化式,则数组长度可以省略
char ch2[] = {'C','S','D','N'};//编译器会利用数组初始化式中初始值的个数来自动确定数组长度,为5
(2)处理字符数组
遍历输入输出字符数组
#include<stdio.h>
#define SIZE 5
int main(){
char ch[SIZE];
for (int i = 0; i < SIZE; i++){
scanf("%c", &ch[i]);//%c是字符的占位符
}
for (int i = 0; i < SIZE; i++) {
printf("%c", ch[i]);
}
}
在C语言中,字符串都是以’\0’结尾的,也就是说一个字符数组第5位是’\0’字符,那么其实有效字符为4个
同时,C语言也可以用一维数组的方式来存放字符串.
char ch[] = {'C','h','i','n','a'};//字符数组,但不是字符串,因为缺少表示字符串结束的'\0'字符
char ch2[] = {'C','h','i','n','a','\0'};//字符串
同时,C语言也允许以字符串常量的形式为字符数组指定初始值
char str[] = {"china"};
char str[] = "china";//两者等价
//注意:在生ing用于存放字符串的一维字符数组时,要保证数组长度比字符串要多1个字符,用于存放'\0'字符,如果没有给'\0'预留位置,会出现不可预知的错误
例如正常的声明字符数组
#define SIZE 50
#include <stdio.h>
int main(){
char str[SIZE+1];//最后加的那一个位置,是用来存放'\0'字符的
}
在C语言中,还可以通过字符指针来处理字符串.字符指针指向字符串常量,或者指向存放字符串的字符数组
char* p = "world";
//等价于
char str[] = "world";
char*p = str;
//解释:字符指针指向了字符串的首字符即'c'
//目的:改变str字符中存放的字符串
//数组名是一个常量指针,为它们直接赋值时错误的,但是可以通过字符指针p来让字符数组指向新的字符串
str = "hello";//错误
p = "hello";//正确
(3)处理二维字符数组
当我们需要处理多个字符串的时候,我们可以通过二维数组来处理
#include <stdio.h>
int main(){
char ch[][5] = {"china","csdn","app","moudle","mode"};//字符的平面展开是6行8列,每一行存放的是一个字符串,这时字符数组的长度为6,每一个字符占用8个字节,如果为单个字符,那么每个字符占用1个字节。每一个字符串末尾都有一个结束符'\0'.
char* p[5] = {"china","csdn","app","moudle","mode"};//声明一个字符指针,每行里面存放的是一个字符指针,指向一个字符串。
}
声明二维字符数组是行长度可以不用声明,但是列长度必须要声明,因为编译器要确定每一个二维数组存放的长度,行长度可以由编译器自行计算。
(4)字符串基础操作
#include<stdio.h>
#define SIZE 10
int main(){
char ch[SIZE+1];//多一个数组元素位置来存放'\0'结束符
// scanf("%s", ch);//正确,ch数组名存放的是字符数组中第一个元素的地址值,所以不用&取地址符
//scanf会直到读入空白或回车的时候结束。
//如"go to school"这时候ch就只有go这个字符串了
//所以为了解决这个问题,产生了get()函数
gets(ch);//gets()函数会把整个字符串存放到
puts(ch);//和printf没有什么太大的区别,但是输出完之后会自动换行
/*结果:
asd dsad
asd dsad
*/
}
(5)字符串常用处理函数
这些在介绍标准库函数时讲过,这里就讲讲实操,必须要包含string.h头文件
函数原型说明 | 功能 | 返回值 |
---|---|---|
char *strcat(char *s1,char *s2) | 把字符串s2接到s1后面 | s1所指地址 |
char *strchr(char *s,int ch) | 在s所指字符串中,找出第一次出现字符ch的位置 | 返回找到的字符的地址,找不到返回NULL |
int strcmp(char *s1,char *s2) | 对s1和s2所指字符串进行比较 | s1<s2,返回负数;s1= =s2,返回0;s1>s2,返回正数 |
char *strcpy(char *s1,char *s2) | 把s2指向的串复制到s1指向的空间 | s1 所指地址 |
unsigned strlen(char *s) | 求字符串s的长度 | 返回串中字符(不计最后的’\0’)个数 |
char *strstr(char *s1,char *s2) | 在s1所指字符串中,找出字符串s2第一次出现的位置 | 返回找到的字符串的地址,找不到返回NULL |
(1)strlen(求字符串长度函数)
函数原型 unsigned int strlen (char *s);//至少为0,所以是无符号整型
参数说明 s为指定的字符串
函数说明 strlen()用来计算指定的字符串s 的长度,不包括结束字符”\0”。
返回值 返回字符串s 的字符数
length = strlen("chinese");//length的值为7
length = strlen("");//length的值为0
(2)strcpy(字符串复制函数)
#include<stdio.h>
#define SIZE 10
#include <string.h>
int main(){
char str1[SIZE+1];
char str2[SIZE+1];
/*1.strcpy函数
原型:strcpy(str1,str2);
功能:将字符串str2中的字符复制到字符串str1的字符中
*/
//strcpy主要解决无法为字符数组赋值的问题
//str1 = "hello";//错误
//str2 = str1;//错误
strcpy(str1,"hello");//正确
puts(str1);
/*2.strncpy函数
原型:strncpy(str1,str2,n);
功能:将字符串str2中的前n个字符复制到字符串str1的字符中
*/
strncpy(str2,str1,3);//正确 将str1中的前3个字符复制到str2中
puts(str2);
/*结果:
hello
hel
*/
}
(3)strcat(字符串连接函数)
#include<stdio.h>
#include<string.h>
int main(){
char s1[] = "abc";
char s2[] = "def";
printf("%s\n",strcat(s1, s2));//将s2连接到s1之后,必须确保s1有足够的空间来存放s2的内容。
//结果:abcdef
}
(4)strcmp(字符串的比较函数)
#include<stdio.h>
#include<string.h>
int main(){
char s1[] = "abc";
char s2[] = "def";
/*strcmp
字符串比较函数,用于比较两个字符串是否相同
它是按照ASCII的值大小进行比较的.比较两个字符串的首字母,如果相同则比较下一个字符
*/
printf("%d",strcmp(s1,s2));//在ascii码中a<d,所以结果为:-1
}
(7)结构、联合(未写)和链表
(1)结构类型和结构变量
声明结构类型的格式:
#include<stdio.h>
#include <string.h>
int main(){
/*
struct 结构名{
数据类型 结构成员名1;
数据类型 结构成员名2;
数据类型 结构成员名3;
};
结构名也同样符合标识符的命名准则,关键字struct 和结构名一起组成了结构标记
注意 :结束花括号}的后面一定要加上";"号,表示结构类型声明结束。
结构成员是变量,里面就跟我们平时命名变量一样
数据类型可以随意,如int,char,double等等都可以
例如:
*/
struct student{
int number;//学号
char name[8];//存放姓名,定义了一个字符数组
char sex;//存放性别
double score[3];//分数,score[0]为存语文成绩,score[1]为存数学成绩,score[2]为存英语成绩,
};
pritnf("==========分割线============");
/*
当结构类型的某个成员的数据类型是另一个结构类型时,就是嵌套结构类型了,我们可以让一个学生中多一个生日日期。
*/
struct student {
int number;//学号
char name[8];//存放姓名,定义了一个字符数组
char sex;//存放性别
double score[3];//分数,score[0]为存语文成绩,score[1]为存数学成绩,score[2]为存英语成绩,
struct date {
int year;
int month;
int day;
}birthday;//这种格式之后会讲
};
}
在声明结构类型后,还需要使用结构标记来声明结构变量,然后才能通过结构变量来访问结构成员。
声明结构变量有3种方式;
//1.先定义结构,再说明结构变量
struct stu{
int num;
char name[20];
int age;
};
struct stu stu1,stu2;
//2.在定义结构类型的同时说明结构变量。
struct stu{
int num;
char name[20];
int age;
}stu1,stu2;
//3.直接说明结构变量
struct{
int num;
char name[20];
int age;
}stu1,stu2;
在声明了结构变量后,通过成员选择运算符 “.” 来访问该结构变量中的结构成员,一般形式如下:
结构变量名.结构成员名
例如:
struct point stu1;
stu1.num = 2;
stu1.age = 18;
stu1.name = "优快云";
**注意事项:**除了使用赋值运算符实现两个同类型的结构变量之间的整体复制外,其他运算符均不能用于两个结构变量之间的整体运算。,也不能直接对结构变量进行整体输入\输出,只允许对结构变量各成员进行输入\输出。
scanf("%d%d",&stu1.num,&stu1.age);
printf("学生学号:%d,学生年龄:%d",stu1.num,stu1.age);
(2)结构和指针
结构和指针的关系
1.指针可以作为结构成员的数据类型
2.可以指向结构变量的指针,简称结构指针,可以使用 & 取地址运算符得到结构变量的地址
struct point{//point是指针、指向的意思
double x;
double y;
};
struct point num1 = {2.12,4.56};
struct point *p;//声明一个结构指针
p = &num1;//p是指向结构变量num1的指针
//访问操作
//1.通过结构指针和成员选择运算符"."来访问结构成员num1
(*p).x = 8.45;//等价于num1.x = 8.45;
//注意!!1 成员选择运算符"."的左边应该是一个结构变量,而不是一个结构指针,p是结构指针,但是对它解引用之后就是等价于结构变量了。
//注意!!2 *p外面的括号不能没有,因为有锈迹的问题,"."的运算符高于"*",如果没有则变成了*(p.x),先执行括号内的东西,并不是指加了一层括号。
//2.通过结构指针和成员选择运算符"->"来访问结构成员
p->x = 8.45;//等价于num1.x = 8.45;
(3)结构和数组
我们通过前面的知识,清楚了结构可以用来存放一个学生,即声明一个结构变量student1,结构变量存放一个姓名,年龄,成绩等,但是我们要存放一个班的信息,这未免太过麻烦,所以这是我们需要能存放50个元素的结构数组来存放信息。
struct student studentArray[50];//假设已经声明好了结构体,声明了结构数组 studentArray ,结构数组的每个元素存放的是结构类型的数据,而不是整型、浮点型、字符型等基本类型的数据。
//在声明结构数组的时候,可以使用数组初始化式给数组元素赋初始化.
struct point num2[4] = {{1.2,1.4},{2.2,2.4},{3.2,3.4},{3.2,3.4}};
//通过结构指针来指向结构数组
struct point num3[4];
struct point *p = num3;
p->x = 4.3;
p->y = 4.5;
(p+1)->x = 5.3;//指向下一个数组元素,将结构成员x赋值为5.3
(p+1)->y = 5.6;
/*等价于
(*p).x = 4.3;
(*p).y = 4.5;
(*(p+1)).x = 5.3;//指向下一个数组元素,将结构成员x赋值为5.3
(*(p+1)).y = 5.6;
*/
声明:联合这一部分,还未完全掌握
(4)链表
一个线性表是若干个数据元素的集合,其中的含义根据实际应用而不同。
特点:存在唯一的的一个头元素,存在唯一的一个尾元素,集合中的每一个元素均只有一个“前驱”,除了尾元素外,集合中的每一个元素均只有一个“后继”。(前驱即存放数据的地方,即变量的值,后继则是指向下一个结点的地址).
用一个指针指向链表的第一个结点(头结点),即头指针
链表的尾节点包含一个空指针NULL,表示这个链表结束
注意:链表不可以向数组一样通过下标随机访问元素的能力
(1)链表和数组的区别:
链表的优点:处理元素更加方便灵活,即长度不需要固定,可以进行增加和删减,链表元素不一定要在内存中连续存放地址值,链表元素之间的顺序可以由指针来实现。
链表的缺点:为了能方便增删,每个结点的尾节点存放链表下一个结点的指针,所以导致了每个数据元素都要有多一个内存空间,来指向下一个结点,所以会导致内存空间比数组更多。
(2)声明结点:
建立一个单向链表,首先需要一个表示结点的结构,这种结构一般分为两个部分,一个或多个需要保存的数据和一个指向同类型的结构的指针,该指针用于建立不同结点之间的关系。
struct node{
int num;
struct node* next;
}
//节点成员next具有struct node*类型,存放一个指向node结构的指针
(3)链表的基本操作
(1)创建结点
在声明了结点的结构类型后,还需要使用结构标记来声明结点的结构变量,然后才能通过结构变量来访问结点。
struct node *head = NULL;//head-->NULL
//解释:声明了一个指向链表头结点的结构变量(结构指针)head并初始化为null,表明链表初始为空,
//注意:链表中所有结点都通过动态存储分配的方式而得到。
//如
head = (struct node *)malloc(sizeof(struct node));//将node结构的大小内存分配给head。可以通过成员选择运算符“->”来访问该内存存储中的结点num和next,使用该运算符前面是个结构指针。
//如图: head--->num next
head->num = 88;
head->next = NULL;
实例:建立一个通讯录表单,能够进行一系列操作。
#define _CRT_SECURE_NO_WARNINGS//vs中为了scanf不报错的条件,也有其他办法,之前有讲
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<windows.h> //系统头文件,用于不自动关闭程序,要按一次键盘按键才能退出
#define PASS 123456//定义初始密码
int len = 1;//定义人数 ,初始化1个人了
//定义结构体:联系人
struct Contact{
int id;//存放人数编号
char name[8];//存放姓名
char sex[8];//存放性别
char phoneNumber[25];//存放手机号
char address[32];//存放地址
//定义下一个联系人
Contact *next;
};
void color(int x){ //设置字体颜色,网上搜索得来
if(x>=0 && x<=15){
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
}
else{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
}
//申请结构体指定大小的内存:创建空链表头结点内
struct Contact *first=(struct Contact *)malloc(sizeof(struct Contact));
//登录函数
int login() {
int pass,k = 0;//k为密码错误次数
for(int i = 1; i<=3; ++i) {
printf("请输入密码: ");
scanf("%d",&pass);
if(pass == PASS) {
printf("*****************************登录成功");
break;
}
else {
if(i!=3){
printf("请重新输入密码,还有%d次输入密码\n",3-i);
}else{
return -1;
}
}
}
// //传递给主函数值,为-1为3次输入全错 ,1即密码输入途中或直接正确
// if(k == 3){
// return -1;
// }
// else{
// return 1;
// }
}
//添加联系人
void add(){
printf("开始添加联系人\n");
//寻找尾巴结点位置
struct Contact *people = first;
while(people->next != NULL){
people = people->next;
}
//声明一个结构指针,用以存放新的联系人的内存空间
struct Contact *people1=(struct Contact *)malloc(sizeof(struct Contact));
//输入新联系人数据
int id = 0;
char name[8];
char sex[8];
char phoneNumber[25] = "0";
char address[32];
printf("请建立联系人编号(不推荐重复):");
scanf("%d",&id);
printf("请输入联系人姓名:");
scanf("%s",name);
printf("请输入联系人性别(男;女):");
scanf("%s",sex);
printf("请输入联系人手机号:");
scanf("%s",phoneNumber);
printf("请输入联系人地址(省名市名):");
scanf("%s",address);
people1->id = id;
people1->next = NULL;
strcpy(people1->name,name);
strcpy(people1->sex,sex);
strcpy(people1->phoneNumber,phoneNumber);
strcpy(people1->address,address);
//把新联系人添加到链表尾部中
people->next = people1;
len=len+1;
printf("通讯录人数:%d\n",len);
}
//查找联系人
void find(){
printf("查找");
char name[8] = "";
printf("请输入想要查找的联系人姓名:");
scanf("%s",name);
//寻找中间节点位置
struct Contact *people = first;
while(people->next != NULL){
// printf("21321321321!");
people = people->next;
//找到 输入的名字
if(strcmp(people->name,name)==0) {
printf(" ***************查询成功!***************\n");
printf("\n");
printf("=====编号========姓名==性别=====手机号=========地址====== \n");
printf(" %d ",people->id);
printf(" %s ",people->name);
printf(" %s ",people->sex);
printf(" %s ",people->phoneNumber);
printf(" %s\n",people->address);
break;
}
}
// printf("现在人数%d\n",len);
if(strcmp(people->name,name)!=0){
color(4);
printf("抱歉,没有找到此联系人!\n");
color(7);
}
if(people->id<=len){
return;
}
}
//删除联系人
void delte(){
int b=0;
char name[8] = "";
printf("输入要删除的联系人姓名:");
scanf("%s",name);
//寻找要删除的结点的前一个结点位置
struct Contact *people=first;
// while(people->next->name!=name) {
while(strcmp(people->next->name,name) != 0){
people=people->next;
b++;
if(b==len){
color(4);
printf("抱歉,没有找到此联系人!\n");
color(7);
delte();
return;
}
}
//如果找到了
if(strcmp(people->next->name,name) == 0){
//清除想要删掉的联系人的内存空间
free(people->next);
//要删除的结点的前一个结点等于下一个结点的下一个结点
people->next = people->next->next;
len = len - 1;
printf(" ***************删除成功!***************\n");
}
// if(people->next==NULL) {
// printf("没有找到此联系人!");
// }
// }
return;
}
//更新联系人
void update(){
int b=0;
char name[8] = "";
printf("输入要修改的联系人姓名:");
scanf("%s",name);
//寻找中间结点位置
struct Contact *people=first;
while(people->next != NULL){
// printf("21321321321!");
people = people->next;
b++;
//找到 输入的名字
if(strcmp(people->name,name)==0) {
printf("找到此联系人。\n");
break;
}
if(b==len){
color(4);
printf("抱歉,没有找到此联系人!\n");
color(7);
update();
return;
}
}
char sex[8];//存放性别
char phoneNumber[25];//存放手机号
char address[32];//存放地址
int id=0;
printf("请建立联系人新编号(不推荐重复):");
scanf("%d",&id);
printf("请输入联系人新的姓名:");
scanf("%s",name);
printf("请输入联系人新的性别(男;女):");
scanf("%s",sex);
printf("请输入联系人新的手机号:");
scanf("%s",phoneNumber);
printf("请输入联系人新的地址(省名市名):");
scanf("%s",address);
people->id = id;
strcpy(people->name,name);
strcpy(people->sex,sex);
strcpy(people->phoneNumber,phoneNumber);
strcpy(people->address,address);
printf("修改联系人成功!\n");
return;
}
//初始化联系人
void load(){
struct Contact *people1=(struct Contact *)malloc(sizeof(struct Contact));
people1->id = 1;
strcpy(people1->name,"王小平");
strcpy(people1->sex , "男");
strcpy(people1->phoneNumber,"13567919504");
strcpy(people1->address,"浙江省杭州市");
people1->next = NULL;
//把1个联系人加入链表
first->next = people1;
}
//显示联系人
void show(){
//定义要查看链表的初始结点
struct Contact *people=first;
//提示语句
printf("=====编号========姓名==性别=====手机号=========地址====== \n");
//循环输出显示
while(people->next!=NULL) {
//指针后移动
people=people->next;
printf(" %d ",people->id);
printf(" %s ",people->name);
printf(" %s ",people->sex);
printf(" %s ",people->phoneNumber);
printf(" %s\n",people->address);
}
}
//退出程序
int exit(){
printf("*****************************结束程序*****************************\n");
printf("\n");
color(6);
printf(" ◆作者◆\n");
printf(" 戴学宜、王佳伟、刘圳、楼钰宇、赵明辉、顾旭旭 ");
color(7);
system("pause");
exit(EXIT_SUCCESS);
}
//保存并退出程序
void save(){
printf("\n保存文件\n");
//打开文件
FILE *file = fopen("通讯录.txt","w+");
//寻找中间节点位置
struct Contact *people = first;
while(people->next != NULL){
people = people->next;
printf("找到联系人!\n");
fputc(48+people->id,file);
fputc(32,file);//打印空格进入
fputc(32,file);
fputc(32,file);
fputs(people->name,file);
fputc(32,file);
fputc(32,file);
fputc(32,file);
fputs(people->sex,file);
fputc(32,file);
fputc(32,file);
fputc(32,file);
fputs(people->phoneNumber,file);
fputc(32,file);
fputc(32,file);
fputc(32,file);
fputs(people->address,file);
fputc(10,file);//换行
}
fclose(file);
printf("保存联系人成功\n");
exit();
}
//界面
int main() {
system("mode con cols=70 lines=30");
printf("**************************欢迎进入通讯录系统**************************\n");
if(login()==-1){
color(4);
printf("密码多次错误,强制退出!");
color(7);
system("pause");
return 0;
}
load();
while(1){
printf("*****************************\n");
printf("\t\tmenu:\n");
printf("\t\t1:添加联系人(已有%d名联系人)\n",len);//1
printf("\t\t2:查找联系人\n");//1
printf("\t\t3:删除联系人\n");
printf("\t\t4:修改联系人\n");
printf("\t\t5:显示联系人\n");//1
printf("\t\t6:保存并退出通讯录\n");//1
printf("\t\t7:退出通讯录\n");//1
//功能
int n;//想要输入的功能
printf("请输入功能编号:");
scanf("%d",&n);
if(n == 1){
add();
}
if(n == 2){
find();
}
if(n == 3){
delte();
}
if(n == 4){
update();
}
if(n == 5){
show();
}
if(n == 6){
save();
}
if(n == 7){
exit();
break;
}
}
return 0;
}
(8)文件
在上个实例中,我们看到了文件的相关操作,其实就是往我们的系统磁盘中存放进一些结果。实际中我们可以存放很多数据进文件中,文件的保存形式可以多种多样,docx,txt,等等都可以。
它包含在stdio.h头文件中。
(1)文件操作
1.打开文件
对任何文件,我们肯定要打开文件,我们需要stdio中的fopen函数
格式:FILE *fopen(const char *filename,const char *mode);//mode指什么格式打开文件
打开方式 | 含义 | 指定文件不存在时 | 指定文件存在时 |
---|---|---|---|
r | 只读方式打开文本文件 | 出错 | 正常打开 |
w | 只写方式打开文本文件 | 建立新文件 | 文件原有内容丢失 |
a | 追加方式打开文本文件 | 建立新文件 | 在原有内容末尾追加 |
r+ | 读/写方式打开文本文件 | 出错 | 正常打开 |
w+ | 读/写方式创建新的文本文件 | 建立新文件 | 文件原有内容丢失 |
a+ | 读/追加方式建立新的文本文件 | 建立新文件 | 在原有内容末尾追加 |
rb | 只读方式打开二进制文件 | 出错 | 正常打开 |
wb | 只写方式打开二进制文件 | 建立新文件 | 文件原有内容丢失 |
ab | 追加方式打开二进制文件 | 建立新文件 | 在原有内容末尾添加 |
rb+ | 读/写方式打开二进制文件 | 出错 | 正常打开 |
wb+ | 读/写方式创建新的二进制文件 | 建立新文件 | 文件原有内容丢失 |
ab+ | 读/追加方式创建新的二进制文件 | 建立新文件 | 在原有内容末尾追加 |
2.关闭文件
格式:int fclose(FILE *stream)
说明:如果成功关闭了文件,fclose则返回0,否则返回EOF。参数stream是文件指针,该文件指针来说fopen
注意:打开了一个文件,则必须要关闭一个文件。
3.文件输入
格式:fscanf(文件指针,格式串,输入项表);
说明:fscanf函数返回按照给定的格式串从文件中正确读入的数据的个数,如果读入过程中发生错误则返回EOF。
4.文件输出
格式:fprintf(文件指针,字符串);
第二种格式:fprintf(文件指针,格式串,输出项表);
如
fp = fopen("test.txt","w");
fprintf(fp,"%s","adsadoijfresf");
uct Contact *people = first;
while(people->next != NULL){
people = people->next;
printf("找到联系人!\n");
fputc(48+people->id,file);
fputc(32,file);//打印空格进入
fputc(32,file);
fputc(32,file);
fputs(people->name,file);
fputc(32,file);
fputc(32,file);
fputc(32,file);
fputs(people->sex,file);
fputc(32,file);
fputc(32,file);
fputc(32,file);
fputs(people->phoneNumber,file);
fputc(32,file);
fputc(32,file);
fputc(32,file);
fputs(people->address,file);
fputc(10,file);//换行
}
fclose(file);
printf("保存联系人成功\n");
exit();
}
//界面
int main() {
system(“mode con cols=70 lines=30”);
printf(“欢迎进入通讯录系统\n”);
if(login()==-1){
color(4);
printf("密码多次错误,强制退出!");
color(7);
system("pause");
return 0;
}
load();
while(1){
printf("*****************************\n");
printf("\t\tmenu:\n");
printf("\t\t1:添加联系人(已有%d名联系人)\n",len);//1
printf("\t\t2:查找联系人\n");//1
printf("\t\t3:删除联系人\n");
printf("\t\t4:修改联系人\n");
printf("\t\t5:显示联系人\n");//1
printf("\t\t6:保存并退出通讯录\n");//1
printf("\t\t7:退出通讯录\n");//1
//功能
int n;//想要输入的功能
printf("请输入功能编号:");
scanf("%d",&n);
if(n == 1){
add();
}
if(n == 2){
find();
}
if(n == 3){
delte();
}
if(n == 4){
update();
}
if(n == 5){
show();
}
if(n == 6){
save();
}
if(n == 7){
exit();
break;
}
}
return 0;
}
## (8)文件
在上个实例中,我们看到了文件的相关操作,其实就是往我们的系统磁盘中存放进一些结果。实际中我们可以存放很多数据进文件中,文件的保存形式可以多种多样,docx,txt,等等都可以。
它包含在stdio.h头文件中。
### (1)文件操作
#### 1.打开文件
对任何文件,我们肯定要打开文件,我们需要stdio中的fopen函数
格式:FILE *fopen(const char *filename,const char *mode);//mode指什么格式打开文件
| 打开方式 | 含义 | 指定文件不存在时 | 指定文件存在时 |
| :------: | :---------------------------: | :--------------: | :----------------: |
| r | 只读方式打开文本文件 | 出错 | 正常打开 |
| w | 只写方式打开文本文件 | 建立新文件 | 文件原有内容丢失 |
| a | 追加方式打开文本文件 | 建立新文件 | 在原有内容末尾追加 |
| r+ | 读/写方式打开文本文件 | 出错 | 正常打开 |
| w+ | 读/写方式创建新的文本文件 | 建立新文件 | 文件原有内容丢失 |
| a+ | 读/追加方式建立新的文本文件 | 建立新文件 | 在原有内容末尾追加 |
| rb | 只读方式打开二进制文件 | 出错 | 正常打开 |
| wb | 只写方式打开二进制文件 | 建立新文件 | 文件原有内容丢失 |
| ab | 追加方式打开二进制文件 | 建立新文件 | 在原有内容末尾添加 |
| rb+ | 读/写方式打开二进制文件 | 出错 | 正常打开 |
| wb+ | 读/写方式创建新的二进制文件 | 建立新文件 | 文件原有内容丢失 |
| ab+ | 读/追加方式创建新的二进制文件 | 建立新文件 | 在原有内容末尾追加 |
#### 2.关闭文件
格式:int fclose(FILE *stream)
说明:如果成功关闭了文件,fclose则返回0,否则返回EOF。参数stream是文件指针,该文件指针来说fopen
注意:打开了一个文件,则必须要关闭一个文件。
#### 3.文件输入
格式:fscanf(文件指针,格式串,输入项表);
说明:fscanf函数返回按照给定的格式串从文件中正确读入的数据的个数,如果读入过程中发生错误则返回EOF。
#### 4.文件输出
格式:fprintf(文件指针,字符串);
第二种格式:fprintf(文件指针,格式串,输出项表);
如
```c
fp = fopen("test.txt","w");
fprintf(fp,"%s","adsadoijfresf");