C语言高级篇

函数

函数入门

为什么需要函数?

请完成这样一个需求:输入两个数,再输入一个运算符加减乘除,得到结果。

先使用传统方式来解决,看看有什么问题没有:

代码冗余(过多重复代码)

不利于代码的维护

复用性困难

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>

int main() {
int num1 = 0, num2 = 0;
double res = 0.0;
char oper = '-'; //运算符
printf("请输入num1的值:");
scanf("%d", &num1);
printf("请输入num2的值:");
scanf("%d", &num2);
printf("请输入运算符号:");
scanf("%c", &oper);//第一个接收回车符
scanf("%c", &oper);
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num1 / num2;
break;
default:
printf("运算符错误");
break;
}
printf("结果是:%.2f\n",res);
//如果我们想再计算一次,还需要再敲一遍所有代码,引出函数
}

函数概念

为完成某一功能的程序指令(语句)的集合,称为函数。

在C语言中,函数分为 自定义函数系统函数

函数还有其他叫法,比如方法等。

1
2
3
4
5
6
7
8
返回类型 函数名 (形参列表);//首先在主方法或头位置定义方法声明

int main(){}

返回类型 函数名 (形参列表){//函数头
执行语句...;//函数体
return 返回值;//可选
}

形参列表:表示函数的输入

函数中的语句:表示未来实现某一功能的代码块。

函数可以有返回值,也可以没有,如果没有返回值,返回类型声明为 void

函数如果写在主方法上面,可以不需要声明函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>

double cal(int a,int b,char oper);//定义方法声明

int main() {
int num1 = 0, num2 = 0;
char oper = ' '; //运算符
printf("请输入num1的值:");
scanf("%d", &num1);
printf("请输入num2的值:");
scanf("%d", &num2);
printf("请输入运算符号:");
scanf("%c", &oper);//第一个接收回车符
scanf("%c", &oper);
double res = cal(num1,num2,oper);
printf("结果是:%d\n",res);
return 0;
}

//函数名cal
//返回值double
double cal(int num1,int num2,char oper){
double res = 0.0;
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num1 / num2;
break;
default:
printf("运算符错误");
return 0.0;
}
return res;
}

c语言函数调用

函数调用机制

为了更好的理解函数调用的过程,展示两个案例,还有示意图。

传入一个数+1 test函数

计算两个数,并返回 getSum函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void test(int n);

int main() {
int num = 6;
test(num);
return 0;
}

//函数test,没有返回值,完成一个数+1
void test(int n){
int n2 = n + 1;
printf("n2 = %d\n",n2);
}

1669646401990

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int getSum(int num1,int num2);

int main() {
int res = getSum(6,8);
printf("%d\n",res);
return 0;
}

//函数getSum,返回值int,完成两数相加
int getSum(int num1,int num2){
return num1 + num2;
}

1669646854100

函数的递归调用

一个函数在函数体中又调用了本身,我们称为递归调用

递归调用快速入门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>

void test(int n){//如果函数写在主方法上面,可以不需要声明函数
if(n > 2){
test(n-1);
}else{
printf("n=%d\n",n);
}
}

int main(){
test(4);
return 0;
}

斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>

int fbn(int n){
if(n == 1 || n == 2){
return 1;
}else{
return fbn(n-1) + fbn(n-2);
}
}
//斐波那契数
//给您一个整数n,求出他的斐波那契数是多少?
//如果n=1,n=2时,返回1
//从n=3开始,对应的斐波那契数是前面两个数的和
int main(){
int num = fbn(7);//res = 13
printf("%d\n",num);
return 0;
}

求函数式结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>

int f(int n){
if(n==1){
return 3;
}else{
return 2*f(n-1)+1;
}
}
//求函数值 已知f(1)=3;f(n)=2*f(n-1)+1;
//请使用递归的思想编程,求出f(n)的值
//因为该题的公式已经给出,所以直接套用
int main(){
int num = f(30);//res = 13
printf("%d\n",num);
return 0;
}

猴子吃🍑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>

int peach(int day){
if(day==10){
return 1;
}else{
return (peach(day + 1) + 1) * 2;
}
}
//猴子吃桃子
//有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃的时候,发现只有一个桃子了,问:最初有多少个桃子?
//分析:第十天有一个桃子,第九天=第十天的桃子*2+1,第八天=第九天的桃子*2+1
int main(){
int n = 1;
int num = peach(n);//res = 13
printf("第%d天有%d个桃子\n",n,num);
return 0;
}


函数细节

  • 函数的形参列表可以是多个。
  • C语言传递参数可以是值传递,(pass by value),也可以传递指针(a pointer passed by value )也叫引用传递
  • 函数的命名遵循标识符命名规范,首字母不能是数字,可以采用驼峰法或者下划线,比如getMax() get_max()
  • 函数中的变量是局部的,函数外不生效。
  • 基本数据类型默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
  • 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
  • C语言不支持函数重载
  • C语言支持可变参数类型,(int num,…)类似
1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>

void t(int *p){
(*p)++;
}
int main(){
int n = 9;
t(&n);//传入了一个指针对象,通过地址访问的方式在函数中修改值,会影响全局变量
printf("%d\n",n);//10
return 0;
}

头文件

头文件概念!

先看一个需求:

在实际的开发中,我们往往需要在不同的文件中,去调用其他文件的定义函数,比如hello.c中,去使用myfun.c文件中的那函数,如何实现?

  • 头文件是拓展名.h的文件,包含了C函数声明和宏定义,被多个源文件中引用和共享。有两种类型的头文件,程序编写的头文件和C标准库自带的头文件。
  • 在程序中要使用头文件,需要使用C预处理命令#include来引用它。前面我们已经看过stdio.h文件,他是C标准库自带的头文件
  • #include叫做文件包含命令,用来引入对应的头文件(.h文件).#include也是C语言预处理命令中的一种。
  • #include的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件链接起来,但是我们不会直接在源文件中复制头文件的内容,因为这么做容易出错,特别是在程序中多个源文件组成的时候。
  • 建议把所有的常量,宏,系统全局变量和函数原型写在头文件中,在需要的时候可以随时引用这些头文件。

主文件

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include "myfun.h"

int main() {
int n1 = 10;
int n2 = 50;
char oper = '+';
double res = 0.0;
res = mycal(n1,n2,oper);
printf("res=%.2f\n",res);
return 0;
}

myfun.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<stdio.h>

//声明函数
int mycal(int n1,int n2,char oper);

//实现声明函数
//double mycal(int n1,int n2,char oper);

int mycal(int num1,int num2,char oper){
double res = 0.0;
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num1 / num2;
break;
default:
printf("运算符错误");
return 0.0;
}
return res;
}

C语言调用自己写的头文件

在C89中,头文件写的是方法声明,有对应的源文件去实现方法

在C99中,在头文件中写方法声明,和实现方法。

头文件的注意事项

引用头文件相当于复制头文件的内容

源文件的名字可以不和头文件一样,但是为了好管理,一般头文件名和源文件名一样。

C语言中include<>include “” 的区别

include<> 引用的是遍历器的类库路径里面的头文件,用于引用系统头文件。

include ”“ 引用的是你程序目录的相对路径中的头文件,如果在程序目录中没有找到引用的头文件则到编译器的类库路径的目录下找该头文件,用于引用用户头文件,

所以:引用系统头文件,两种形式都可以,include<>效率高,引用用户头文件,只能使用include””

一个#include命令只能包含一个头文件,多个头文件需要多个#include命令

同一个头文件如果被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制

在一个被包含的文件(.c)中有可以包含另一个文件头文件(.h)

在c89不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。

作用域和静态关键字

C的内存布局图

1669950417047

变量作用域

所谓变量作用域(Scope),就是指变量的有效范围

函数内部声明、定义的局部变量,作用域仅限于函数内部

函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名会优先使用局部变量

在一个代码块,比如 for/if 中的局部变量,那么这个变量的作用域就在该代码块

在所有函数外部定义的变量叫全局变量,作用域在整个程序有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int n = 20;//函数外部定义的变量,就是全局变量
void sayHello(){
char name[] = "tom";
printf("hello %s \n",name);
}
//当局部变量和全局变量同名时,以局部变量为准(就近原则)
void f10(int n){//函数形参,会被视为f10的局部变量
printf("n=%d\n",n);
}

int main() {
sayHello();
printf("hello %s \n",name);//这里调用是不允许的

f10(10);
return 0;
}

初始化局部和全局变量

局部变量,系统不会对其进行初始化,必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出。

全局变量,系统会自动对其进行初始化,如下所示

数据类型 初始化默认值
int 0
char ‘\0’
float 0.0
double 0.0
pointer null

正确的初始化变量是一个良好的编程习惯,否则有时候程序可能产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int j;//这里会初始化一个值
float f;
double d1;


int main() {
int i;//必须初始化才能使用
printf("%d %.2f %.2f\n",j,f,d1);
return 0;
}

作用域的细节

全局变量 global variable 保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也就是所有的代码文件,包括源文件 (.c文件) 和头文件 (.h文件)。

局部变量 local variable 保存在栈中,函数被调用时才动态的为变量分配存储单元,它的作用域仅限于函数内部

c语言规定,只能从的作用域向的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量

在同一作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用编译器采用就近原则

由 { } 包围的代码块也拥有独立的作用域。f

静态关键字!

static关键字在C语言中比较常用,使用恰当能够大大提高程序的模块化特性,有利于拓展和维护。

局部变量被static修饰后,我们称为静态局部变量

对应静态局部变量在声明时未赋值,编译器也会把它初始化为0

静态局部变量存储于进程的静态存储区(全局性质,只会被初始化一次,即使函数返回,他的值也会保持不变)

全局变量使用static修饰

普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器认为它们是同一个变量),静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。

定义不需要与其他文件共享的全局变量时,加上static关键字能够有效降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

void fn(void){
int n = 10;
printf("n=%d\n",n);
n++;
printf("n=%d\n",n);
}

void fn_static(){
static int n = 10;
printf("static n=%d\n",n);
n++;
printf("n++=%d\n",n);
}

int main() {
static int n;//就是一个静态局部变量,默认初始化值为0
printf("n=%d",n);
return 0;
}

function.h

1
2
3
int num = 5;
static int num1 = 6;

main.c

1
2
3
4
5
6
7
8
#include <stdio.h>
#include "function.h"

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

函数使用static修饰

函数使用方式和全局变量类似,在函数的返回类型前加上static,就是静态函数,

非静态函数可以在另一个文件通过extern引用

静态函数只能在声明它的文件中可见,其他文件不能引用该函数

不同的文件可以使用相同名字的静态函数,互不影响

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include "function.h"

int main() {
fun();
fun2();
return 0;
}

function.h

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

void fun(){//非静态函数
printf("hello from fun1\n");
}

static void fun2(){//静态函数
printf("hello from fun2\n");
}

常用函数

字符串函数

得到字符串的长度 *size_t strlen(const char str) 计算字符串str的长度,知道空结束字符,单不包括空结束字符

拷贝字符串 *char * strcopy(char dest,const char * src) 把src所指向的字符串复制给dest

连接字符串 **char strcat(char dest,const char * src) 把src所指向的字符串追加到dest所指向的字符串的结尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>//声明字符串相关的函数

int main() {
char src[50],dest[50];//定义了两个字符数组(字符串)
char *str = "abcddf";
int a = strlen(str);//计算字符串的长度
strcpy(src, "hello");//拷贝字符串会将原来的内容覆盖
strcpy(dest,"你好");
strcat(dest, src);//是将src的内容拼接到dest的字符串后面
printf("src=%s\n",src);
printf("str.len=%d\n",a);
printf("dest=%s\n",dest);
return 0;
}

日期时间函数

获取当前时间 *char ctime(const time_t * timer) 返回一个表示当地时间字符串,当地时间是基于参数timer。

编写一段代码来统计函数test执行的时间

double difftime(time_t time1,time_t time2) 返回time1和time2之间相差的秒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/types/time_t.h>
#include <stdio.h>
#include <time.h>

void test(){//运行test函数,看看执行花费的时间
int i = 0;
int sum = 0;
while(i < 300000000){
sum = 0;
sum+=i*2;
i++;
}
}

int main() {
time_t curtime;//time_h是一个结构体类型
time(&curtime);//time()可以完成初始化
printf("当前时间=%s",ctime(&curtime));//返回一个表示当地时间的字符串,当地时间是基于参数的timer
//先得到test执行前的时间
time_t start,end;
double diff_t;//存放时间差
printf("启动程序\n");
time(&start);//初始化得到当前时间
test();
//再得到test执行后的时间,两个时间差就是耗费时间
time(&end);
diff_t = difftime(end, start);
printf("花费的时间:%.2f\n",diff_t);
return 0;
}

数学函数

double exp(double x) 返回e的x次幂的值

double log(double x) 返回x的自然对数(基数为e的对数)

double pow(double x,double y) 返回x的y次幂

double sqrt(double x) 返回x的平方根

double fabs(double x) 返回x的绝对值

数据类型和字符串类型转换

sprintf函数

sprintf函数打印到字符串中,而printf函数打印输出到屏幕上。sprintf函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛

该函数包含在stdio.h的头文件中

如果想要字符串类型转基本数据类型,通过<stdlib.h>的函数调用 atoi atof即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main() {
char str1[20];
char str2[20];
char str3[20];
int a = 20984,b = 48090;
double d = 14.309949;
sprintf(str1, "%d %d",a,b);
sprintf(str2, "%.2f",d);
sprintf(str3, "%8.2f",d);
printf("str1=%s str2=%s str3=%s\n",str1,str2,str3);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>

int main() {
char str[10] = "123456";
char str2[10] = "12.67423";
char str3[2] = "A";
char str4[4] = "111";
int num1 = atoi(str);//将str转为一个整数
short s1 = atoi(str4);
double d = atof(str2);//将str转为一个小数
char c = str3[0];//获取到str3数组的第一个元素
printf("num1=%d d=%f c=%c s1=%d",num1,d,c,s1);
return 0;
}

预处理和宏定义

预处理介绍

使用库函数之前,使用#include引入对应的头文件,这种以#号开头的命令成为预处理命令

这些在编译之前对源文件进行简单加工的过程,就称为预处理。

预处理主要是处理以#开头的命令,预处理命令要放在所有函数之外,而且一般都放在源文件中

预处理是C语言中一个重要功能,由预处理程序完成。当面对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕后自动进入对源程序的编译

C语言提供了多种预处理功能,如宏定义,文件包含,条件编译。合理使用他们编写程序便于阅读,修改,移植,调试。利于模块化设计。

命令快速入门

开发一个C语言程序,让它暂停5秒以后输出内容“hello”,并且要求跨平台也能运行

Windows平台的暂停函数原型是 void Sleep(DWORD dwMilliseconds); 参数的单位是”毫秒“,位于<windows.h>头文件

Linux平台的暂停参数的原型是 unsigned int sleep(unsigned int seconds); 参数的单位是”秒“,位于<unistd.h>头文件

#if #elif #endif 就是预处理命令,他们都是在编译之前由预处理程序来执行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#if _win32
#include <windows.h>
#elif _linux
#include <unistd.h>
#endif

int main(){
//不同的平台下调用不同的函数
#if _win32
Sleep(5000);
#elif _linux
sleep(5);
#endif
puts("hello");
return 0;
}

宏定义

#define叫做宏定义,它也是C语言预处理命令的一种,所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。

#表示这是一条预处理命令,所有的预处理命令都已#开头。宏名是标识符的一种,命名规则和变量相同。可以是数字,表达式,if语句,函数等

程序中反复利用表达式就可以使用宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdio.h>
#define M (n*n+3*n)//宏名M,对应的字符串(n*n+3*n)

int main(){
int sum,n;
printf("input a number:");
scanf("%d",&n);
sum = 3*M+4*M+5*M;//宏展开:3*(n*n+3*n)+4*(n*n+3*n)+5*(n*n+3*n)
printf("sum=%d\n",sum);
return 0;
}

宏定义细节

宏定义使用宏名来表示的一个字符串,在宏展开的时候又以该宏名取代字符串,这只是一种简单的替换。字符串中可以含任何字符,它可以是常数,表达式,if语句,函数等,预处理程序对它不做任何检查,如有错误,只能在编译已被宏展开后的源程序发现。

宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号一起替换

宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束,如要终止其作用域可以使用**#undef**命令

代码中的宏名如果被引号包围,那么预处理程序不对其作为宏代替

宏定义允许嵌套,在宏定义中的字符串可以使用已经定义的宏名,在宏展开时由预处理程序层层代换

习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。

可用宏定义表示数据类型,使书写方便男

宏定义表示数据类型和用 typedef 定义数据说明符的区别:宏定义只是简单的字符串替换,由预处理器来处理,而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

1
2
3
4
5
#define UINT unsigned int//用宏定义无符号整型数字类型
void main(){
UINT a,b;//宏替换
}

带参数的宏定义

C语言允许宏带有参数,在宏定义中的参数称为”形式参数“,在宏调用中的参数称为”实际参数“,这点和函数有些类似

对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参

带参宏定义的一般形式为:#define 宏名 (形参列表)字符串, 在字符串中可以包含多个形参

带参的宏调用的一般形式为:宏名(实参列表);

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define MAX(a,b) (a>b)?a:b //MAX就是带参数的宏,ab就是形参,在字符串中有宏的参数列表
int main(){
int x,y,max;
printf("input two numbers:");
scanf("%d %d",&x,&y);
max = MAX(x,y);//宏替换时(预处理,有预处理器),会进行字符串的替换,同时使用实参去替换形参
//MAX(x,y)宏展开后 (x>y)?x:y
printf("max=%d\n",max);
return 0;
}

带参宏定义细节

带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现

1
2
3
4
#define MAX(a,b) (a>b)?a:b 如果写成了 #define MAX (a,b) (a>b)?a:b
将被认为是无参宏定义,宏名MAX代表了字符串 (a,b) (a>b)?a:b
而不是:MAX(a,b) 代表(a>b)?a:b了

在带参宏定义中,不会为形式参数分配内存,因此不必指名数据类型。而在宏调用中,实参包含了具体的数据,要用他们去替换形参,因此实参必须要指明数据类型

在宏定义中,字符串内的形参通常要用括号。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
#define SQ(y) (y)*(y)//想要计算这个y的平方,必须要用(),否则可能结果会出错
int main(){
int a sq;
printf("input a number:");
scanf("%d",&a);
sq = SQ(a+1);//宏替换 (a+1) * (a+1)
printf("%d\n",sq);
system("pause");
return 0;
}

带参宏定义和函数的区别!

宏展开仅仅是字符串的替换,不会对表达式计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。

函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码

案例c说明:要求使用函数计算平方值,使用宏计算平方值,并总结两者的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#define SQ(y) ((y) * (y))

int SQ1(int y){
return ((y) * (y));
}

int main(){
int i = 1;
while(i <= 5){
printf("%d^2=%d\n",i,SQ1(i));
printf("%d^2=%d\n",i,SQ(i));
i++;
}
return 0;
}

C语言预处理命令总结

预处理指令是以#开头的代码行,#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令在编译器进行编译之前对源代码做某些转换

指令 说明
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已经定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束要给#if…..#else条件代码块

预处理使用注意

预处理功能是C语言特有的功能,它是在对源程序正式编译前有预处理程序完成的,程序员在程序中用预处理命令来调用这些功能。

宏定义可以带有参数,宏调用时是以实参代换形参,而不是“值传送”。

为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。

文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。

条件编译允许只编译源文件中满足的条件程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率

使用预处理功能便于程序的修改,阅读,移植和调试,也便于实现模块化程序设计。

数组

基本介绍

有一个养鸡场有6只鸡,体重分别为1kg,1.2kg,1.4kg,2kg,2.5kg,2.1kg。请问六只鸡的总体重是多少?平均体重是多少?请你编写一个程序.

传统方案,定义6个变量,统计和,并求出平均值,不灵活,效率慢,从而引出数组

数组可以存放多个同一类型数据。数组也是一种数据类型,是构造类型。传递是以引用的方式传递(即传递的是地址)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

int main(){
double hens[6];
hens[0] = 1.0;
hens[1] = 1.2;
hens[2] = 1.4;
hens[3] = 2.0;
hens[4] = 2.5;
hens[5] = 2.1;
double totalW = 0.0;
double avgW = 0.0;
int arrlen = sizeof(hens) / sizeof(double);//数组占用的总大小除以一个double占用大小就能获得数组的总长度
int i;
for(i = 0;i<arrlen;i++){
totalW += hens[i];
}
printf("总体重是:%.2f\n",totalW);
avgW = totalW / arrlen;
printf("平均体重是:%.2f\n",avgW);
}

数组定义内存布局

数据类型 数组名 【数组大小】;

int a[5] ; //a数组名,类型int,[5] 大小,即a数组最多存放5个int数据

赋值 a[0] = 1;

数组名就代表该数组的首地址,即a[0]地址

数组的各个元素是连续分布的,假如a[0]地址0x1122 a[1]地址0x1122+int字节数(4) = 0x1126

后面a[2]地址=a[1]地址+int字节数(4)= 0x1126 + 4 = 0x112A,以此类推

访问数组元素是从0开始下标的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(){
double arr[5];
int arrlen = sizeof(arr) / sizeof(double);
for(int i = 0;i<arrlen;i++){
printf("请输入一个小数:");
scanf("%lf",&arr[i]);
}
// 输出整个数组
for(int i = 0;i<arrlen;i++){
printf("arr[%d]=%.2f\n",i,arr[i]);
}
}

初始化数组的方式

1
2
3
4
5
6
7
8
9
10
int main(){
int arr[3];
arr[0] = 100;
arr[1] = 200;

int arr2[3] = {4,5,6};

int arr3[] = {2,4,4,5};
}

数组注意事项和细节

数组是多个相同类型数据的结合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化。

数组创建后,如果没有赋值,则遵守如下规则

全局数组默认值0

非全局数组初值是机器垃圾值(即:原来系统分配给这块空间的值)

使用数组的步骤 1.定义数组 2.给数组各个元素赋值 3.使用数组

数组的下标是从0开始的

数组下标必须在指定范围内使用,编译通过,在运行时会因为数组越界而异常中断,比如 int arr[5] 有效下标为 0-4

c的数组属构造类型,是引用传递(传递的是地址),因此当把一个数组传递给一个函数时,函数操作数组会影响到原数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

void f1(int arr[]){
printf("f1函数中的arr地址=%p\n",arr);
arr[0] += 1;
}

int main(){
int arr[3] = {3,4,5};
printf("main函数中的arr地址=%p\n",arr);
int i;
f1(arr);//数组默认是指针传递
for(i = 0;i<3;i++){
printf("arr[%d]=%d\n",i,arr[i]);
}
return 0;
}

字符数组

用来存放字符的数组,看几个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char a[10];

char b[5][10];//二维数组

#include <stdio.h>

int main(){
char c[20] = {'c',',','p','r','o','g','r','a','m'};//给部分元素赋值
char str[3] = {'a','b','c'};
char str1[4] = {'a','b','c','\0'};//这样就可以解决输出错误的问题
char str2[] = {'j','a','c','k'};//这个后面也不会自动添加\0
//从第一个字符开始输出,直到读取到\0,表示结束读取字符串,认为字符串结束
printf("%s\n",c);
printf("%s\n",str);//abcc,program ? 输出错误
return 0;
}


字符数组实际上是一系列字符的集合,也就是字符串String,在C语言中,没有专门的字符串数量,没有string类型,通常就用一个字符数组来存放一个字符串。

在C语言中,字符串实际上是使用null字符(‘\0’)终止的一维字符数组,因此,一个以null结尾的字符串,包含了组成字符串的字符。

‘\0’是ascii码表中的第0个字符,用NUL表示,称为空字符,该字符即不能表示,也不是控制字符,输出该字符不会有任何效果,它在C语言中仅作为字符串的结束标志。

结论:如果在给某个字符数组,赋值时,赋值数量应该小于数组长度大小-1,则会自动在最后面加\0表示字符串结尾。否则会出现输出错误

表示形式和内存布局

用字符数组存放一个字符串

1
2
3
char str[] = "hello tom";
char str2[] = {'h','e'};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
int main(){
char greeting[] = "hello";
printf("greeting=%s\n",greeting);
int len = strlen(greeting);
printf("len=%d\n",len);
printf("第三个字符是:%c\n",greeting[3]);
for(int i = 0;i < len;i++){
printf("%c\t",greeting[i]);
}
printf("\n");
return 0;
}

1669875648356

用字符指针指向一个字符串

比如char* str = “hello tom”;

C语言对字符串常量 “hello tom”是按照字符数组处理的,在内存中开辟了一个字符数组用来存放字符串常量,程序在定义字符串指针变量str时只是把字符串首地址(即存放字符串的字符数组的首地址)赋给str

printf(”%s\n”,str);可以输出str指向的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>

int main() {
//使用一个指针pStr,指向一个字符数组
char *pStr = "tom";
printf("pstr的地址:%p和指向的地址:%p\n",&pStr,pStr);
pStr = "hello tom";
printf("pstr的地址:%p和指向的地址:%p\n",&pStr,pStr);
printf("pStr 指向的字符串=%s\n", pStr);
return 0;
}

1669876166939

字符串的表示形式

使用字符指针变量和字符数组两种方法的区别

字符数组由若干个元素组成,每个元素放一个字符;而字符指针变量中存放的是地址(字符串/字符数组的首地址),绝不是将字符串放到字符指针变量中(是字符串首地址)

对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值

1
2
3
4
char str[13];
str = "hello tom";
str[0] = 'i';

对字符指针变量,采用下面方法赋值,是可行的

1
2
3
char* a;
a = "hello tom";

数组相关函数

函数
strcpy(s1,s2);复制字符串s2到字符串s1
strcat(s1,s2);拼接字符串s2到s1的末尾
strlen(s1);返回字符换s1的长度
strcmp(s1,s2);如果s1和s2是相同的,则返回0,如果s1<s2返回小于0,如果s1>s2返回大于0
strchr(s1,ch);返回一个指针,指向字符串s1中字符ch第一次出现的位置
strstr(s1,s2);返回一个指针,指向字符串s1中字符串s2的第一次出现的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>

int main() {
char str1[12] = "hello";
char str2[12] = "world";
char str3[12];
int len;
//copy str1 for str3
strcpy(str3, str1);
printf("strcpy(str3,str1):%s\n",str3);
//concat str1 and str2
strcat(str1,str2);
printf("strcat(str1,str2):%s\n",str1);
//alter concat,str1 total length
len = strlen(str1);
printf("strlen(str1):%d\n",len);
return 0;
}

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>

int main() {
int arr[] = {3,0,-1,30,6};
int len = sizeof(arr) / sizeof(int);
for(int i = 0;i<len-1;i++){//总比较轮数
for(int j = 0;j<len-1-i;j++){//每轮比较的次数
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for(int i = 0;i<len;i++){
printf("%d\t",arr[i]);
}
printf("\n");
return 0;
}

顺序查找

顺序查找就是穷举法,将整个数组遍历,一个一个的查找,直到找到目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <string.h>

int seqSearch(int arr[], int arrlen, int val) {
for (int i = 0; i < arrlen; i++) {
if (arr[i] == val) {
return i;
}
}
return -1;
}

int main() {
int arr[] = {23, 1, 35, 89, 101};
int val;
printf("请输入一个数:");
scanf("%d", &val);
int arrlen = sizeof(arr) / sizeof(int);
int index = seqSearch(arr, arrlen, val);
printf("%d\n", index);
return 0;
}

二分查找

二分查找是先找出数组的中间值,然后根据当前中间值和要查找的数字相比大小,从而判断下一次定义中间值的方向。最后就能找到要找的数字,相对顺序查找,这种查找效率极高,步数相对也少了大半,是一个不二之选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<stdio.h>

//二分查找是在有序的数组中进行的
//先找到数组中间的这个数,midVal,和findVal比较
//如果>,说明比他小,需要向 左边查找 如果<,说明比他大 需要向 右边查找
int binarySearch(int arr[],int leftIndex,int rightIndex,int findVal){
int midIndex = (leftIndex + rightIndex) / 2;
int midVal = arr[midIndex];
if(midVal > findVal){
binarySearch(arr, leftIndex, midIndex-1, findVal);
}else if(midVal < findVal){
binarySearch(arr, midIndex+1, rightIndex, findVal);
}else{
return midIndex;
}
if(leftIndex > rightIndex){
printf("失败,没有找到");
return -1;
}
return 114514;
}

int main(){
printf("请输入要查找的数:");
int val;
scanf("%d",&val);
int arr[] = {1,3,5,7,9,11,13,15,17};
int arrlen = sizeof(arr) / sizeof(int);
val = binarySearch(arr, 0, arrlen-1, val);
printf("%d\n",val);
return 0;
}

二维数组

以下代码展示了二维数组在空间中地址分布情况

二维数组在内存的存在形式,各个元素的地址是连续分布的,即在前一个元素基础上+4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>

int main(){
int a[4][6];

for(int i = 0;i<4;i++){
for(int j = 0;j<6;j++){
a[i][j] = 1;
}
}
for(int i = 0;i<4;i++){
for(int j = 0;j<6;j++){
printf("%d",a[i][j]);
}
}
printf("\n");
//看看二维数组的内存布局
printf("二维数组的首地址=%p\n",a);
for(int i = 0;i<4;i++){
for(int j = 0;j<6;j++){
printf("二位数组中a[%d][%d]的地址是:%p\n",i,j,&a[i][j]);//二维数组开辟的是一块连续的数组空间,由每个一维数组的头和尾地址都相差4个字节,一维数组中的每个元素的地址也相差4个字节
}
}
return 0;
}

直接初始化

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

int a[4][6] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};//会自动匹配到各行各列

二维数组的细节

可以只对部分元素赋值,未赋值的元素自动取“零”值

如果对全部元素赋值,那么第一堆的长度可以不给出。比如:

1
2
3
4
int a[3][3] = {1,2,3,4,5,6,7,8,9};
可以写为:
int a[][3] = {1,2,3,4,5,6,7,8,9};

二维数组可以看作是由一维数组嵌套而成的,如果一个数组的每个元素又是一个数组,那么他就是二维数组

指针

指针基本介绍

指针是C语言的精华,也是C语言的难点。

指针,也就是内存的地址,所谓指针变量,也就是保存了内存地址的变量。关于指针的基本使用,在讲变量的时候做了入门级的介绍

获取变量的地址,用&,比如:int num = 10,获取num的地址:&num

指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:int *ptr = &num;ptr就是指向int类型的指针变量,即ptr是int *类型。

获取指针类型所指向的值,使用:*,比如:var ptr *int,使用 *ptr 获取 ptr 指向的值

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明,指针变量声明的一般形式为:

1
2
3
4
5
int *ip;//一个整型的指针
double *dp;//一个double型的指针
float *fp;//一个float型的指针
char *ch;//一个字符型的指针

指针的算术运算

指针是一个用数值表示的地址。可以对指针执行算术计算。可以对指针进行四种算术运算:

++ – + -

指针递增操作(++)

  • 数组在内存中是连续分布的
  • 当对指针进行++时,指针会按照它指向的数据类型字节数大小增加,比如 int *指针,每++,就增加4个字节;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

const int MAX = 3;//常量
int main(){
int var[] = {10,100,200};
int i,*ptr;//ptr是一个int*指针
ptr = var;//ptr指向了var数组的首地址
for(i = 0;i<MAX;i++){
printf("var[%d]地址=%p\n",i,ptr);
printf("存储值:var[%d]=%d\n",i,*ptr);
ptr++;//ptr存放值+4个字节(int)
}
return 0;
}

指针递减操作(–)

  • 数组在内存中是连续分布的
  • 当对指针进行–时,指针会按照它指向的数据类型字节数大小减少,比如 int *指针,每–,就减少4个字节;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

const int MAX = 3;//常量
int main(){
int var[] = {10,100,200};
int i,*ptr;//ptr是一个int*指针
ptr = &var[MAX-1];//指针指向最后一个元素的地址
for(i = MAX;i > 0;i--){
printf("var[%d]地址=%p\n",i,ptr);
printf("存储值:var[%d]=%d\n",i,*ptr);
ptr--;
}
return 0;
}

指针+-操作

当可以对指针按照指定的字节数大小进行+或-的操作,可以快速定位你要的地址

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>

int main(){
int var[] = {10,100,200};
int i, *ptr;

ptr = var;//将var的首地址给ptr
ptr+=2;//ptr的存储地址+2个int字节(8个字节)
printf("var[2]=%d var[2]的地址=%p ptr存储的地址 = %p ptr存储地址的值=%d\n",var[2],&var[2],ptr,*ptr);
return 0;
}

指针的比较

指针可以用关系运算符进行比较,如==,<和>。如果p1和p2指向两个变量,比如同一个数组中不同元素,则可对p1和p2进行大小比较,看下面代码,说明输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main(){
int var[] = {10,100,200};
int *ptr;
ptr = var;
if(ptr == var[0]){//这里类型不匹配
printf("ok1");
}
if(ptr == &var[0]){
printf("ok2");
}
if(ptr == var){
printf("ok3");
}
if(ptr >= &var[1]){
printf("ok4");
}
return 0;
}

正常输出ok2,ok3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>

const int MAX = 3;

int main(){
int var[] = {10,100,200};
int i,*ptr;
ptr = var;
i = 0;
while(ptr <= &var[MAX-2]){
printf("Address of var[%d] = %p\n",i,ptr);
printf("Value of var[%d] = %d\n",i,*ptr);
ptr++;
i++;
}
return 0;
}

指针数组

要让数组的元素指向int 或其他数据类型的指针,可以使用指针数组

1
2
数据类型 *指针数组名[大小]

比如 :int *ptr[3]

ptr声明为一个指针数组

由3个整数指针组成,因此,ptr中的每个元素,都是一个指向int值的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
const int MAX = 3;
int main(){
int var[] = {10,100,200};
int i,*ptr[3];
for(i = 0;i < MAX;i++){
ptr[i] = &var[i];
}
for(i = 0;i<MAX;i++){
printf("Value of var[%d]=%d ptr[%d]本身的地址:%p\n",i,*ptr[i],i,ptr[i]);
}
return 0;
}

1669902401628

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main(){
char *books[] = {"三国演义","西游记","红楼梦","水浒传"};
char *pStr = "abc";//不需要&,直接指针指向字符串
int num = 10;
int *pNum = &num;//需要&,指向该变量的地址
int i,len = 4;
for(i = 0;i < len;i++){
printf("book[%d]=%s\n",i,books[i]);//字符串比较特殊,不需要*就可以获得值
}
}

指向指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向int类型指针的指针

1
2
int **ptr; //ptr的类型是 int **

当一个目标被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,比如 **ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

int main(){
int var;
int *ptr;
int **pptr;
var = 3000;

ptr = &var;
pptr = &ptr;

printf("var的地址=%p var=%d\n",&var,var);
printf("ptr的本身的地址=%p ptr存放的地址=%p *ptr=%d\n",&ptr,ptr,*ptr);
printf("pptr本身地址=%p pptr存放的地址=%p **pptr=%d\n",&pptr,pptr,**pptr);
return 0;
}

开始运行...

var的地址=0x7ffe7eb5e278 var=3000
ptr的本身的地址=0x7ffe7eb5e270 ptr存放的地址=0x7ffe7eb5e278 *ptr=3000
pptr本身地址=0x7ffe7eb5e268 pptr存放的地址=0x7ffe7eb5e270 **pptr=3000

运行结束。

1669904147348

传递指针给函数

当函数的形参类型是指针类型时,使用该函数时,需要传递指针,或者地址,或者数组给该形参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void test(int *p){
*p+=1;//*p是访问num的值
}

int main(){
int num = 90;
int *p = &num;
test(&num);//传递地址
printf("main中的num=%d\n",num);
test(p);//传递指针
printf("main中的num=%d\n",num);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

double getAverage(int *arr,int size){
int i,sum = 0;
double avg;
for(i = 0;i<size;i++){
sum += arr[i];
}
avg = sum / size;
return avg;
}

int main(){
int balance[5] = {1000,2,3,17,50};
double avg;
//传递一个指针数组的指针作为参数
avg = getAverage(balance,5);
printf("average value is:%.2f\n",avg);
}

编写一个函数 strlong(),返回两个字符串中较长的一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <string.h>

char *strlong(char *str1,char *str2){
int len1 = strlen(str1);
int len2 = strlen(str2);
printf("str1 length:%d str2 length:%d\n",len1,len2);
if(len1 >= len2){
return str1;
}else{
return str2;
}
}

int main(){
char str1[30],str2[30],*str;
printf("请输入第一个字符串:");
gets(str1);
printf("请输入第二个字符串:");
gets(str2);
str = strlong(str1,str2);
printf("longer string:%s\n",str);
return 0;
}

指针函数的细节

用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量,局部数组和形式参数,函数返回的指针不能指向这些数据

函数运行结束后会销毁所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存完全清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存。

C语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定于局部变量为static变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int *func(){
//int n = 100;//局部变量
static int j = 100;//返回局部变量如果是static性质的,那么n存放的数据空间在静态存储区取
// return &n;
return &j;
}

int main(){
int *p = func();//在func方法结束后,n局部变量被销毁,返回的指针指向的是一个无效的值
int n;
printf("okook~~");//可能使用到局部变量 int n = 100 占用空间
n = *p;
printf("value=%d\n",n);//不一定
return 0;
}

编写一个函数,他会随机生成10个随机数,并使用表示指针的数组名,来返回他们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

//编写一个函数,返回一个一维数组
int *f1(){
static int arr[10];//必须加上static,让arr的空间在静态数据区分配
int i = 0;
for(;i<=10;i++){
arr[i] = rand();
}
return arr;
}

int main(){
int *p = f1();
for(int i = 0;i<10;i++){
// printf("two:%d\n",*p++);
printf("one:%d\n",*(p+i));//建议这种写法
}
return 0;
}

指向函数的指针

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在的内存区域的首地址,这和数组名非常相似。

把函数的这个首地址(或称入口地址) 赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数,这种指针就是函数指针

函数指针定义

returnType(*pointerName)(param list);

returnType为函数返回值类型

pointerName 指针名称

param list为函数参数列表

参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称

注意()的优先级高于*,第一个括号不能省略,如果写作returnType *pointerName(param list);就成了函数原型,它表明函数的返回值类型为 returnType *

用指针来实现对函数的调用,返回两个整数中的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

int max(int a,int b);

int main(){
int x,y,maxVal;
//函数指针的名字pmax
//int表示该函数指针指向的函数是返回int类型
//(int,int)表示 该函数指针指向的函数形参是接收两个int类型
//在定义函数指针时,也可以写上形参名
int (*pmax)(int x,int y) = max;
printf("please enter two numbers:");
scanf("%d %d",&x,&y);
//(*pmax)(x,y)通过函数指针去调用函数max
maxVal = (*pmax)(x,y);
//还有一种调用方式pmax(x,y)
printf("Max Value:%d pmax=%p &pmax=%p\n",maxVal,pmax,&pmax);
return 0;
}

int max(int a,int b){
return a > b ? a : b;
}

指针回调函数

函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。

简单的讲:回调函数是由别人的函数执行时调用你传入的函数,通过函数指针完成

使用回调函数的方式,给一个整形数组int arr[10] 赋10个随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>

//f就是一个函数指针,可以接收的函数是返回int,没有形参的函数
void initArray(int *array,int arraySize,int(*f)(void)){
int i;
for(i = 0;i<arraySize;i++){
//f在这里被initArray调用,充当了回调函数角色
array[i] = f();//通过函数指针调用了getNextRandomValue函数
}
}

int getNextRandomValue(void){
return rand();
}

int main(void){
int myarray[10],i;
//调用了initArray
//传入了一个函数名 getNextRandomValue(地址),需要使用函数指针接收
initArray(myarray, 10, getNextRandomValue);
//输出赋值后的数组
for(i = 0;i<10;i++){
printf("%d ",myarray[i]);
}
printf("\n");
return 0;
}

指针的注意事项细节

指针变量存放的是地址,从这个角度看指针的本质就是地址。

变量声明的时候,如果没有确切的地址赋值,为指针变量赋一个null值是好的编程习惯。

赋为null值的指针被称为空指针,null指针是一个定义在标准库<stdio.h>中的值为零的常量 #define NULL 0

指针使用一览

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(){
int *p = NULL;//p空指针
int num = 34;
p = &num;
printf("*p=%d\n",*p);
return 0;
}

动态内存分配

C程序中,不同数据在内存中分配说明:

全局变量——内存中的静态存储区

非静态的局部变量——内存中的动态存储区——stack栈

临时使用的数据——建立动态内存分配区域,需要时随时开辟,不需要时及时释放——heap堆

根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据,只能通过指针来引用

内存动态分配的相关函数

头文件 #include <stdlib.h> 声明了四个关于内存动态分配的函数

函数原型 void * malloc (usigned int size) (momory allocation)

作用——在内存中的动态存储区(堆)中分配一个长度为size的连续空间。

形参size的类型为无符号整型,函数返回值是所分配区域的第一个字节的地址,即此函数是一个指针型函数,返回的指针指向该分配域的开头位置。

malloc(100);开辟100字节的临时空间,返回值为其第一个字节的地址。

函数原型 *void calloc (unsigned n,unsigned size)

作用——在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组

用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size

函数返回指向所分配域的起始位置的指针,分配不成功,返回null。

p=calloc(50,4);开辟50*4个字节临时空间,把起始地址分配个指针变量p

函数原型:*void free(void p)

作用——释放变量p所指向的动态空间,使这部分空间能重新被其他变量使用。

p是最近一次调用calloc或malloc函数时的函数返回值

free函数无返回值

free(p);释放p所指向的已分配的动态空间

函数原型 **void realloc (void p,unsigned int size)

作用——重新分配malloc或calloc函数获得的动态空间大小,将p指向的动态空间大小改变为size,p的值不变,分配失败返回null

realloc(p,50),将p所指向的已分配的动态空间改为50字节

返回类型说明

C99标准把以上 malloc,calloc,realloc 函数的基类型定位void类型,这种指针称为无类型指针(typeless pointer),即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,仅提供一个纯地址,而不能指向任何具体的对象。

void指针类型

C99标准允许使用基类型为void的指针类型。可以定义一个基类型为void的指针变量(即void *变量),它不指向任何类型的数据,请注意:不要把“指向void类型理解为能指向”任何的类型“的数据,而应理解为”指向空类型“或不指向确定的类型的数据。在将它的值赋给另一指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a = 3;	//定义a为整型变量

int *p1 = &a; //p1指向int型变量

char *p2; //p2指向char型变量

void *p3; //p3为无类型指针变量(基类型为void型)

p3 = (void *)p1; //将p1的值转换为void *类型,然后赋值给p3

p2 = (char *)p3; //将p3的值转换为char *类型,然后赋值给p2

printf("%d\n",*p1); //合法,输出a的值

p3 = &a; printf("%d",* p3); //错误,p3是无指向的,不能指向a

当把void指针赋值给不同基类型的指针变量(或相反)时,编译系统会自动进行转换,不必用户自己进行强制转换,例如;

p3 = &a;

相当于“p3 = (void *)&a;”,赋值后p3得到a的纯地址,但并不指向a,不能通过 * p3输出a的值。

动态创建数组,输入5个学生的成绩,另外一个函数检测低于成绩低于60分的,输出不合格的成绩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdlib.h>
#include <stdio.h>

int main(){
void check(int *);//函数声明
int *p,i;
p = (int *)malloc(5*sizeof(int));//开辟了一个20字节5个int空间到堆区,指针指向这片空间的首地址,在c99可以不使用(int *)进行强转,正常右面的是(void *)类型
for(i = 0;i<5;i++){
scanf("%d",p+i);//输入后给刚才开辟的空间赋值,
}
check(p);
free(p);//销毁p指针指向的堆区空间
return 0;
}

void check(int *p){
int i;
printf("不及格的成绩有:\n");
for(i = 0;i < 5;i++){
if(p[i] < 60){
printf("%d\n",p[i]);
}
}
}

145_尚硅谷_动态内存分配机制和案例_哔哩哔哩_bilibili

动态分配内存的基本原则

避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大

仅在需要时分配内存。只要使用完堆上的内存块,就需要释放它,否则可能出现内存泄漏

总是确保释放以分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存

在释放内存之前,确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏,在循环分配内存时,要特别小心。

指针使用一览

变量定义 类型表示 含义
int i; int 定义整型变量
int *p; int * 定义p为指向整型数据的指针变量
int a[5]; int [5] 定义整型数组a,它有5个元素
int *p[4]; int * [4] 定义指针数组p,它由4个指向整型数据的指针元素组成
int ( * p)[4]; int ( * ) [4] p为指向包含4个元素的一维数组的指针变量
int f ( ); int ( ) f为返回整型函数值的函数
int *p( ); int * ( ) p为返回一个指针的函数,该指针指向整型函数
int (*p)( ); int ( * ) ( ) p为指向函数的指针,该函数返回一个整型值
int **p; int ** p是一个指针变量,它指向一个指向整型数据的指针变量
void * p; void * p是一个指针变量,基类型为void(空类型),不指向具体的对象

结构体和共用体

引出结构体

看一个猫猫问题🐱

张老太太养了两只🐱,一只名字叫小白,今年3岁,白色。还有一只小花❀,今年5岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太太没有这只猫猫。

使用传统的技术解决

  • 单独的定义变量解决,很麻烦
  • 我们学习了数组,它是一组具有相同类型的数据集合。在编程中,我们往往还需要一组类型不同的数据,例如猫的名字使用字符串,年龄是int,因为数据类型不同,不能用一个数组来存放

引出结构体

结构体入门

1669985396605

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

int main(){
//创建结构体cat 数据类型
struct Cat{//结构体的名字Cat,Cat就是我们自己构造的一个数据类型
char *name;//名字,使用指针,指向一个字符串
int age;
char *color;
};
//使用结构体Cat创建对应的变量
struct Cat cat1;//cat1就是Cat的一个变量
//给cat1的成员赋值
cat1.age = 5;
cat1.name = "小白";
cat1.color = "红色";
struct Cat cat2;//cat2也是Cat的一个变量
cat2.age = 3;
cat2.name = "小花";
cat2.color = "花色";
printf("第一只猫:名字:%s 年龄:%d 颜色%s\n",cat1.name,cat1.age,cat1.color);
printf("第二只猫:名字:%s 年龄:%d 颜色%s\n",cat2.name,cat2.age,cat2.color);
return 0;
}

结构体和结构体变量区别

通过上面的案例和讲解我们可以看出:

结构体是自定义的数据类型,表示的是一种数据类型。

结构体变量代表一个具体变量,类似

1
2
3
int num1;//int是数据类型,而num1是一个具体的int变量
struct Cat cat1;//Cat是结构体数据类型,而cat1是一个Cat变量

Cat就像一个模板,定义出来的结构体变量都含有相同的成员,也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。

结构体变量内存布局

1669987063118

声明结构体和成员

1
2
3
4
struct 结构体名称{ //结构体名首字母大写,比如Cat
成员列表;
}

1
2
3
4
5
6
7
8
struct Student{
char *name;//姓名
int num;//学号
int age;//年龄
char group;//所在学习小组
float score;//成绩
}

从叫法上看:有些书上称为成员,有些书上说结构体包含的变量

成员是结构体的一个组成部分,一般是基本数据类型,也可以是数组,指针,结构体等。比如我们前面定义Cat结构体的int age 就是一个成员。

成员的注意事项细节

成员声明语法同变量,示例:数据类型成员名;

字段的类型可以为:基本类型,数组或指针,结构体等

在创建一个结构体变量后,需要给成员赋值,如果没有赋值就使用可能导致程序异常终止。

不同结构体变量的成员是独立,互不影响,一个结构体变量的成员更改,不影响另外一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main(){
struct Cat{
char *name;
int age;
char *color;
};
struct Cat cat1;//没有初始化输出会报错
printf("名字=%s 年龄=%d 颜色=%s",cat1.name,cat1.age,cat1.color);
return 0;
}

结构体定义形式

先定义结构体,然后再创建结构体变量

1
2
3
4
5
6
7
8
9
10
11
struct Stu{
char *name;
int num;
int age;
char group;
float score;
};
struct Stu str1;
//定义了两个变量str1和str2,他们都是Stu类型,都由5个成员组成
//注意关键字struct不能少

再定义结构体的同时定义结构体变量

1
2
3
4
5
6
7
8
9
struct Stu{
char *name;
int num;
int age;
char group;
float score;
}str1,str2;
//再定义结构体Stu的同时,创建了两个结构体变量str1和str2

匿名结构体:如果只需要str1,str2两个变量,后面不需要再使用结构体名定义其他变量,再定义时也可以不给出结构体名

1
2
3
4
5
6
7
8
9
10
struct {//没有写Stu
char *name;
int num;
int age;
char group;
float score;
}stu1,stu2;//该结构体数据类型,被称为匿名结构体,stu1,和stu2就是该结构体的两个变量

stu1.name = "tom";str1.num = 100;

成员的获取和赋值

结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义,数组使用下标[ ]获取单个元素,结构体使用点号获取单个成员。获取结构体成员的一般格式为:

1
2
结构体变量名.成员名;

赋值的一种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main() {
struct {
char *name;
int num;
int age;
char group;
float score;
} stu1 = {"xxx", 11, 18, 'B', 89.5}, stu2 = {"yyy", 12, 19, 'C', 93.1};
//stu2 = {"zzz", 12, 19, 'C', 93.1};//报错,匿名只能结构体尾部这样声明
printf("stu1.name=%s\n",stu1.name);
return 0;
}

编写一个Dog结构体,包含name,age,weight属性

编写一个say函数,返回字符串,方法返回信息中包含所有成员值

在main方法中,创建Dog结构体变量,调用say函数,将调用结果打印输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>

struct Dog{
char *name;
int age;
double weight;
};

char *say(struct Dog dog){
//将这个信息放入到一个字符串(字符数组)
static char info[50];//局部静态变量
sprintf(info,"name=%s age=%d weight%.2f",dog.name,dog.age,dog.weight);
dog.name = "小花";//尝试修改dog的name
return info;
}

int main() {
//定义结构体变量
struct Dog dog;
char *info = NULL;
dog.name = "小黄";
dog.age = 3;
dog.weight = 32.3;
info = say(dog);//结构变量默认是值传递
printf("%s\n",info);
printf("name=%s",dog.name);//值传递在方法中更改不会发生变化
return 0;
}

编程创建一个Box结构体,在其中定义三个成员表示一个立方体的长,宽,高,长宽高可以通过控制台输入。

定义一个函数获取立方体的体积(volume)

创建一个结构体,打印给定尺寸的立方体的体积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

struct Box{
int length;
int width;
int high;
int volume;
};

int volume(struct Box box){
return box.length * box.width * box.high;
}

int main(){
char *l[] ={"长","宽","高"};
int s[3];
for(int i = 0;i<3;i++){
printf("请输入正方形的%s:",l[i]);
scanf("%d",&s[i]);
}
struct Box box = {s[0],s[1],s[2],0};
box.volume = volume(box);
printf("%d\n",box.volume);
return 0;
}

一个景区根据游人的年龄收取不同价格的门票

请编写游人结构体(visitor),根据年龄决定能够购买的门票价格并输出

规则:年龄>18,门票为20元,其他情况免费

可以循环从控制台输入名字和年龄,打印门票收费情况,如果名字输入n,则退出程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>

struct Vistor{
char *name;
int age;
};

int check(struct Vistor v){
if(strcmp(v.name, "n") == 0){
return -1;
}
if(v.age > 18){
return 20;
}
return 0;
}

int main(){
char *name;
int age;
printf("请输入名字:");
scanf("%s",name);
printf("请输入年龄:");
scanf("%d",&age);
struct Vistor v = {name,age};
int val = check(v);
if(val != -1){
printf("%s,您需要花费%d元来买票\n",v.name,val);
}else{
printf("程序退出\n");
return 0;
}
return 0;
}

共用体引出

现有一张关于学生信息和教师信息的表格。学生信息包括姓名,编号,性别,职业,分数,教师的信息包括姓名,编号,性别,职业,教学科目。请看下面的表格:

name num sex profession score/course
smith 501 woman student 89.5
bob 1001 man teacher math
andy 109 woman teacher english
aili 982 man student 95.0

传统的方法解决

定义结构体,根据人员的职业,使用对应的成员变量。

1
2
3
4
5
6
7
8
9
struct Person{
char name[20];
int num;
char sex;
char profession;
float score; //student used score
char course[20]; //teacher used course
}

传统的方式的问题分析:会造成空间的浪费,比如学生只使用score,但是也占用了course成员的20个字节

解决方案:

做struct Stu 和 struct Tea [但如果职业很多,就会对应多个结构体类型,不利于管理]

使用共用体

共用体(Union)属于构造类型,它可以包含多个类型不同的成员。和结构体非常相似,但是也有不同的地方。

共用体有时也被称为联合或者联合体,定义格式为

1
2
3
4
union 共用体名{
成员列表
};

结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//定义共用体类型和共用体变量的三种方法(和结构体一样)
//1
union data{
int n;
char ch;
double f;
};
union data a,b,c;

//2
union data{
int n;
char ch;
double f;
}a,b,c;

//3
union {
int n;
char ch;
double f;
}a,b,c;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

union data{//data就是一个共用体(内存布局),包含三个成员,共享数据空间,该空间的大小以占用最大的成员为标准
int n;
char ch;
short m;
};

int main(){
union data a;
int s = sizeof(a);
int s1 = sizeof(union data);
printf("%d,%d\n",s,s1);//4,4
a.n = 0x40;//16进制
printf("%d,%c,%d\n",a.n,a.ch,a.m);//三个值都被赋值
a.n = 0x2059;//16进制
printf("%d,%c,%d\n",a.n,a.ch,a.m);
return 0;
}

共用体的特点

共用体数据类型有以下几个特点
1)使用共用体变量的目的是希望用同一个内存段存放几种不同类型的数据,但请注意,在每一个瞬间只能存放其中一种,而不是同时存放几种;
2)能够访问的是共用体变量中最后一次被赋值的成员,在对一个新的成员赋值后原有的成员就失去作用。
3)共用体变量的地址和它各成员的地址都是同一个地址;
4)不能对共用体变量名赋值;不能企图引用变量名来得到一个值;不能在定义共用体变量时对它初始化;不能用共用体变量名作为函数参数。

1670057111647

共用体最佳实践

处理之前的表格,使用共用体完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>

#define TOTAL 3
//定义了一个结构体Person
struct Person {
char name[20];
int num;
char sex; // f -> 女 m -> 男
char profession; // s -> 学生 t -> 老师
union {
float score;
char course[20];
} sc; // sc是一个共用体变量
};

int main() {
int i = 0;
struct Person persons[TOTAL];
for (; i < TOTAL; i++) {
printf("input info:");
scanf("%s %d %c %c", persons[i].name, &(persons[i]).num, &(persons[i]).sex,
&(persons[i]).profession);
if (persons[i].profession == 's') { //如果是学生
printf("请输入学生的成绩:");
scanf("%f", &persons[i].sc.score);
} else { //如果是老师
printf("请输入老师的科目:");
scanf("%s", persons[i].sc.course);
}
fflush(stdin);//刷新
}
printf("Name\tNum\tSex\tPro\tScore/Course\n");
for (i = 0; i < TOTAL; i++) {
if (persons[i].profession == 's') { //如果是学生
printf("%s\t%d\t%c\t%c\t%.2f\n", persons[i].name, persons[i].num,
persons[i].sex, persons[i].profession, persons[i].sc.score);
} else { //如果是老师
printf("%s\t%d\t%c\t%c\t%s\n", persons[i].name, persons[i].num,
persons[i].sex, persons[i].profession, persons[i].sc.course);
}
}
return 0;
}

共用体和结构体的区别Union和typedef区别

家庭收支软件

模拟实现一个基于文本界面的家庭记账软件

掌握初步的编程技巧和调试技巧

主要涉及以下知识点:

局部变量和基本数据类型

循环语句

分支语句

简单的屏幕输出格式控制

该软件能够记录家庭的收入,支出,并能够打印收支明细表

项目采用分级菜单方式。主菜单如下:

1
2
3
4
5
6
7
8
9
10
           ----------家庭收支记账软件-------------
1收支明细
2登记收入
3登记指出
4退出

请选择(1-4):
本次收入金额:1000
本次支出说明:快餐

收支明细的界面及操作过程如下所示:

1
2
3
4
5
----------当前收支明细记录--------------
收支 收支金额 账户金额 说明
收入 1000 11000 劳务费
支出 800 10200 物业费

“退出”的界面及操作过程如下所示:

1
2
3
4
5
6
7
8
		  ----------家庭收支记账软件-------------
1收支明细
2登记收入
3登记支出
4退出
请选择(1-4):4
是否退出(y/n):

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <string.h>

int main() {
//显示菜单
//使用while循环,输入4表示退出
int l = 1;
char key = ' '; //表示用户输入了什么
char details[300] = "收支\t收支金额\t账户金额\t说明\n";
char note[20] = ""; //收入或者支出的说明
double money = 0.0;
double balance = 1000.0; //账户余额
char temp[100] = ""; //用于格式化的单笔收入或支出
int flag = 0; //表示是否有数据
while (l) {
printf(" 家庭收支记账软件\n");
printf("\t1收支明细\n");
printf("\t2登记收入\n");
printf("\t3登记支出\n");
printf("\t4退出系统\n");
printf("请选择(1-4):");
scanf("%c", &key);
getchar(); //过滤回车
switch (key) {
case '1':
if (flag) {
printf("%s", details);
} else {
printf("当前没有明细,来一笔把!\n");
}
break;
case '2':
printf("本次收入的金额:");
scanf("%lf", &money);
getchar(); //过滤回车
printf("本次收入说明:");
scanf("%s", note);
getchar();
balance += money;
//将信息写入temp
sprintf(temp, "收入\t%.2f\t\t%.2f\t\t%s\n", money, balance, note);
strcat(details, temp);
flag = 1;
break;
case '3':
printf("本次支出的金额:");
scanf("%lf", &money);
getchar(); //过滤回车
if (money > balance) {
printf("余额不够\n");
break;
}
printf("本次支出说明:");
scanf("%s", note);
getchar();
balance -= money;
//将信息写入temp
sprintf(temp, "支出\t%.2f\t\t%.2f\t\t%s\n", money, balance, note);
strcat(details, temp);
break;
case '4':
printf("\t退出系统\n");
printf("您真的要退出吗(y/n):");
scanf("%c", &key);
if (key == 'y') {
printf("您退出了系统\n");
l = 0;
}
break;
default:
continue;
}
}
}

优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <stdio.h>
#include <string.h>

//定义一个结构体
struct MyFamilyAccount {
int flag;
char details[1000];
double balance;
};

char key = ' '; //表示用户输入了什么
char note[20] = ""; //收入或者支出的说明
double money = 0.0;
double balance = 3000.0; //账户余额
char temp[100] = ""; //用于格式化的单笔收入或支出
int loop = 1;

void pay(struct MyFamilyAccount *mfa) {
printf("本次收入的金额:");
scanf("%lf", &money);
getchar(); //过滤回车
printf("本次收入说明:");
scanf("%s", note);
getchar();
balance += money;
//将信息写入temp
sprintf(temp, "收入\t%.2f\t\t%.2f\t\t%s\n", money, balance, note);
strcat((*mfa).details, temp);
(*mfa).flag = 1;
}

void income(struct MyFamilyAccount *mfa) {
printf("本次收入的金额:");
scanf("%lf", &money);
getchar(); //过滤回车
printf("本次收入说明:");
scanf("%s", note);
getchar();
balance += money;
//将信息写入temp
sprintf(temp, "收入\t%.2f\t\t%.2f\t\t%s\n", money, balance, note);
strcat((*mfa).details, temp);
(*mfa).flag = 1;
}

void showDetails(struct MyFamilyAccount *mfa) {
if ((*mfa).flag) {
printf("%s", (*mfa).details);
} else {
printf("当前没有明细...来一笔吧!");
}
}

void exits() {
printf("\t退出系统\n");
printf("您真的要退出吗(y/n):");
scanf("%c", &key);
getchar();
if (key == 'y') {
printf("您退出了系统\n");
loop = 0;
}else{
loop = 1;
}
}

void mainMenu(struct MyFamilyAccount *mfa) {
while (loop) {
printf(" 家庭收支记账软件\n");
printf("\t1收支明细\n");
printf("\t2登记收入\n");
printf("\t3登记支出\n");
printf("\t4退出系统\n");
printf("请选择(1-4):");
scanf("%c", &key);
getchar(); //过滤回车
switch (key) {
case '1':
showDetails(mfa);
break;
case '2':
income(mfa);
break;
case '3':
pay(mfa);
break;
case '4':
exits();
break;
default:
continue;
}
}
}

int main() {
//创建一个结构体变量
struct MyFamilyAccount mfa;
mfa.balance = 1000;
mfa.flag = 0;
//使用拷贝的防止
memset(mfa.details, 3000, 1012);
strcpy(mfa.details, "收支\t收支金额\t账户金额\t说明\n");
//调用mainMenu
mainMenu(&mfa);
return 0;
}

CRM系统开发

项目需求说明

模拟实现基于文本界面的(客户信息管理软件)

该软件能够实现对客户对象的插入,修改和删除(用数组实现),并能够打印客户明细表。

主要涉及以下知识点:

结构体的各种操作

引用数组

数组的插入,删除和替换

结构体变量协同工作

项目采用分级菜单方式。主菜单如下:

1
2
3
4
5
6
7
8
	-----------客户信息管理软件-------------
1添加客户
2修改客户
3删除客户
4客户列表
5退出
请选择(1-5):

添加客户的界面如下演示

1
2
3
4
5
6
7
8
-----------添加客户------------
姓名:张胜男
性别:男
年龄:30
电话:010-56253825
邮箱:[email protected]
-----------添加完成------------

删除客户的界面及操作过程如下演示:

1
2
3
4
5
-----------删除客户------------
请选择待删除客户编号(-1退出):
确认是否删除(y/n):y
-----------删除完成------------

客户列表的界面如下所示:

1
2
3
4
5
6
7
----------------客户列表-------------------
编号 姓名 性别 年龄 电话 邮箱
1 张三 男 30 010-562342234 [email protected]
2 李四 女 23 010-324299521 [email protected]
3 王五 女 26 010-183845318 wang@163.com
--------------客户列表完成-----------------

Customer结构体的设计

该类封装客户的以下信息:

int id;客户编号

char *name;客户姓名

char gender;性别

int age;年龄

char *phone;电话

char *email;电子邮箱

基础架构

思路:

Customer结构体含有的成员,应该从给出的界面来分析

分析出有6个成员,数据类型有可以分析

编写一个函数,输出给出的某个Customer变量的信息

customerManage.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

int main() { return 0; }

显示主菜单和退出软件的功能

用户打开软件,可以看到主菜单,输入5退出软件。

在customerManage.c中,编写一个函数mainMenu显示菜单

customerManage.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

int loop = 1;
char key;
void mainMenu() {
do {
printf("客户信息管理软件\n");
printf("添加客户\n");
printf("修改客户\n");
printf("删除客户\n");
printf("客户列表\n");
printf("退出系统\n");
printf("请输入(1-5):");
scanf("%c", &key);
getchar();

switch (key) {
case '1':
printf("添加客户\n");
break;
case '2':
printf("修改客户\n");
break;
case '3':
printf("删除客户\n");
break;
case '4':
printf("客户列表\n");
break;
case '5':
printf("退出系统\n");
loop = 0;
break;
default:
printf("您输入有误,请重新输入\n");
}
} while (loop);
printf("您退出了系统...\n");
}

int main() {
mainMenu();
return 0;
}

项目功能实现-完成显示客户列表的功能

功能说明

展示客户列表

思路分析

因为我们需要将多个客户保存起来,因此我们需要使用结构体数组-Customer结构体数组

编写一个函数listCustomers来显示客户信息

在主菜单调用listCustomer函数即可

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>
#include <string.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

int loop = 1;//控制是否退出主菜单
char key;
int customerNum = 1;//表示当前有多少个客户

//客户结构体数组
struct Customer customers[100];//最多有100个客户,超过100个,可以拓展

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s\n", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

//显示客户信息列表
void listCustomers(){
int i = 0;
printf("----客户列表----\n");
printf("编号\t姓名\t性别\t年龄\t电话\t邮箱\n");
for(i = 0;i<customerNum;i++){//表示客户的编号
getCustomerInfo(&customers[i]);
}
}


void mainMenu() {
do {
printf("客户信息管理软件\n");
printf("添加客户\n");
printf("修改客户\n");
printf("删除客户\n");
printf("客户列表\n");
printf("退出系统\n");
printf("请输入(1-5):");
scanf("%c", &key);
getchar();
switch (key) {
case '1':
printf("添加客户\n");
break;
case '2':
printf("修改客户\n");
break;
case '3':
printf("删除客户\n");
break;
case '4':
printf("客户列表\n");
listCustomers();
break;
case '5':
printf("退出系统\n");
loop = 0;
break;
default:
printf("您输入有误,请重新输入\n");
}
} while (loop);
printf("您退出了系统...\n");
}

int main() {
//测试数据
customers[0].id = 1;
customers[0].age = 10;
strcpy(customers[0].email,"[email protected]");
customers[0].gender = 'f';
strcpy(customers[0].name, "tom");
strcpy(customers[0].phone, "010-1929391");
mainMenu();
return 0;
}

添加客户的功能

思路分析:

编写一个add函数,在该函数中完成添加客户的功能

在mainMenu调用

代码实现

解释:为什么scanf被跳过或不执行_c语言scanf被跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <stdio.h>
#include <string.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

int loop = 1; //控制是否退出主菜单
char key;
int customerNum = 1; //表示当前有多少个客户

//客户结构体数组
struct Customer customers[100]; //最多有100个客户,超过100个,可以拓展

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s\n", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

void add() {
customers[customerNum].id = customerNum + 1; //编号按照自增规则
printf("姓名:");
scanf("%s", customers[customerNum].name);
getchar();
printf("性别:");
scanf("%c", (&customers[customerNum].gender));
getchar();
printf("年龄:");
scanf("%d", (&customers[customerNum].age));
printf("电话:");
scanf("%s", customers[customerNum].phone);
printf("邮箱:");
scanf("%s", customers[customerNum].email);
getchar();
printf("添加完成\n");
customerNum++;//自增
}

//显示客户信息列表
void listCustomers() {
int i = 0;
printf("----客户列表----\n");
printf("编号\t姓名\t性别\t年龄\t电话\t邮箱\n");
for (i = 0; i < customerNum; i++) { //表示客户的编号
getCustomerInfo(&customers[i]);
}
}

void mainMenu() {
do {
printf("客户信息管理软件\n");
printf("添加客户\n");
printf("修改客户\n");
printf("删除客户\n");
printf("客户列表\n");
printf("退出系统\n");
printf("请输入(1-5):");
scanf("%c", &key);
getchar();
switch (key) {
case '1':
printf("添加客户\n");
add();
break;
case '2':
printf("修改客户\n");
break;
case '3':
printf("删除客户\n");
break;
case '4':
printf("客户列表\n");
listCustomers();
break;
case '5':
printf("退出系统\n");
loop = 0;
break;
default:
printf("您输入有误,请重新输入\n");
}
} while (loop);
printf("您退出了系统...\n");
}

int main() {
//测试数据
customers[0].id = 1;
customers[0].age = 10;
strcpy(customers[0].email, "[email protected]");
customers[0].gender = 'f';
strcpy(customers[0].name, "tom");
strcpy(customers[0].phone, "010-1929391");
mainMenu();
return 0;
}

完成删除客户的功能

思路分析:

先编写一个函数findIndex,根据用户输入的id,去查找该id是否存在customer结构体数组中,如果存在,就返回id对应的index,因为id和index并不是对应的关系

编写一个函数del,根据函数接收的index来判断是否可以删除

编写delView函数,让用户可以进行输入来进行删除客户的操作

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <stdio.h>
#include <string.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

int loop = 1; //控制是否退出主菜单
char key;
int customerNum = 1; //表示当前有多少个客户

//客户结构体数组
struct Customer customers[100]; //最多有100个客户,超过100个,可以拓展

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s\n", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

void add() {
customers[customerNum].id = customerNum + 1; //编号按照自增规则
printf("姓名:");
scanf("%s", customers[customerNum].name);
getchar();
printf("性别:");
scanf("%c", (&customers[customerNum].gender));
getchar();
printf("年龄:");
scanf("%d", (&customers[customerNum].age));
printf("电话:");
scanf("%s", customers[customerNum].phone);
printf("邮箱:");
scanf("%s", customers[customerNum].email);
getchar();
printf("添加完成\n");
customerNum++;//自增
}

//根据输入的id,返回index,没有返回-1
int findIndex(int id){
int index = -1;
int i;
for(i = 0;i<customerNum;i++){
if(customers[i].id == id){
index = i;
}
}
return index;
}

//返回int,1表示删除成功,返回0表示删除失败
//接收的是要删除的客户id
int del(int id){
int index = findIndex(id);
if(index == -1){
return 0;
}else{
//删除的本质就是将id编号的这个客户移除
//1.从index+1开始,整体向左移动
for(int i = index+1;i<customerNum;i++){
customers[i-1] = customers[i];//使用index+1位置的customer覆盖为index位置的customer
}
//2.customerNum-1
customerNum--;
return 1;
}
}

//显示删除客户的界面,然后调用
void delView(){
int id;
char c = ' ';
printf("请选择删除客户编号(-1退出):");
scanf("%d",&id);
getchar();
if(id == -1){
printf("你放弃了删除\n");
return;
}
printf("确认是否删除:");
scanf("%c",&c);
getchar();
if(c == 'Y'){
if(del(id)==0){
printf("删除失败,id不存在\n");
}else{
printf("恭喜删除成功\n");
}
}
}


//显示客户信息列表
void listCustomers() {
int i = 0;
printf("----客户列表----\n");
printf("编号\t姓名\t性别\t年龄\t电话\t邮箱\n");
for (i = 0; i < customerNum; i++) { //表示客户的编号
getCustomerInfo(&customers[i]);
}
}

void mainMenu() {
do {
printf("客户信息管理软件\n");
printf("添加客户\n");
printf("修改客户\n");
printf("删除客户\n");
printf("客户列表\n");
printf("退出系统\n");
printf("请输入(1-5):");
scanf("%c", &key);
getchar();
switch (key) {
case '1':
printf("添加客户\n");
add();
break;
case '2':
printf("修改客户\n");
break;
case '3':
printf("删除客户\n");
delView();
break;
case '4':
printf("客户列表\n");
listCustomers();
break;
case '5':
printf("退出系统\n");
loop = 0;
break;
default:
printf("您输入有误,请重新输入\n");
}
} while (loop);
printf("您退出了系统...\n");
}

int main() {
//测试数据
customers[0].id = 1;
customers[0].age = 10;
strcpy(customers[0].email, "[email protected]");
customers[0].gender = 'f';
strcpy(customers[0].name, "tom");
strcpy(customers[0].phone, "010-1929391");
mainMenu();
return 0;
}

完善退出系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <stdio.h>
#include <string.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

int loop = 1; //控制是否退出主菜单
char key;
int customerNum = 1; //表示当前有多少个客户

//客户结构体数组
struct Customer customers[100]; //最多有100个客户,超过100个,可以拓展

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s\n", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

void add() {
customers[customerNum].id = customerNum + 1; //编号按照自增规则
printf("姓名:");
scanf("%s", customers[customerNum].name);
getchar();
printf("性别:");
scanf("%c", (&customers[customerNum].gender));
getchar();
printf("年龄:");
scanf("%d", (&customers[customerNum].age));
printf("电话:");
scanf("%s", customers[customerNum].phone);
printf("邮箱:");
scanf("%s", customers[customerNum].email);
getchar();
printf("添加完成\n");
customerNum++; //自增
}

//根据输入的id,返回index,没有返回-1
int findIndex(int id) {
int index = -1;
int i;
for (i = 0; i < customerNum; i++) {
if (customers[i].id == id) {
index = i;
}
}
return index;
}

//返回int,1表示删除成功,返回0表示删除失败
//接收的是要删除的客户id
int del(int id) {
int index = findIndex(id);
if (index == -1) {
return 0;
} else {
//删除的本质就是将id编号的这个客户移除
// 1.从index+1开始,整体向左移动
for (int i = index + 1; i < customerNum; i++) {
customers[i - 1] =
customers[i]; //使用index+1位置的customer覆盖为index位置的customer
}
// 2.customerNum-1
customerNum--;
return 1;
}
}

//显示删除客户的界面,然后调用
void delView() {
int id;
char c = ' ';
printf("请选择删除客户编号(-1退出):");
scanf("%d", &id);
getchar();
if (id == -1) {
printf("你放弃了删除\n");
return;
}
printf("确认是否删除:");
scanf("%c", &c);
getchar();
if (c == 'y') {
if (del(id) == 0) {
printf("删除失败,id不存在\n");
} else {
printf("恭喜删除成功\n");
}
}
}

//退出系统的方法
void exits() {
char c;
while (1) {
printf("您真的要退出程序吗:");
scanf("%c", &c);
getchar();
if (c == 'y' || c == 'n') {
if (c == 'y') {
printf("您退出了系统\n");
loop = 0;
return;
} else {
return;
}
} else {
printf("输入有误,请重新输入\n");
}
}
}

//显示客户信息列表
void listCustomers() {
int i = 0;
printf("----客户列表----\n");
printf("编号\t姓名\t性别\t年龄\t电话\t邮箱\n");
for (i = 0; i < customerNum; i++) { //表示客户的编号
getCustomerInfo(&customers[i]);
}
}

void mainMenu() {
do {
printf("客户信息管理软件\n");
printf("添加客户\n");
printf("修改客户\n");
printf("删除客户\n");
printf("客户列表\n");
printf("退出系统\n");
printf("请输入(1-5):");
scanf("%c", &key);
getchar();
switch (key) {
case '1':
printf("添加客户\n");
add();
break;
case '2':
printf("修改客户\n");
break;
case '3':
printf("删除客户\n");
delView();
break;
case '4':
printf("客户列表\n");
listCustomers();
break;
case '5':
printf("退出系统\n");
exits();
break;
default:
printf("您输入有误,请重新输入\n");
}
} while (loop);
}

int main() {
//测试数据
customers[0].id = 1;
customers[0].age = 10;
strcpy(customers[0].email, "[email protected]");
customers[0].gender = 'f';
strcpy(customers[0].name, "tom");
strcpy(customers[0].phone, "010-1929391");
mainMenu();
return 0;
}

修改客户信息的操作

思路分析:

需要写一个update方法,用来让用户输入的更新指定客户的界面

首先使用find方法先查找是否输入合法,否则不能更新,无误后

使用change方法改变 –id位置的客户信息

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#include <stdio.h>
#include <string.h>

//结构体数据类型
struct Customer {
int id;
int age;
char name[10];
char gender; // f,m
char phone[16];
char email[16];
};

int loop = 1; //控制是否退出主菜单
char key;
int customerNum = 1; //表示当前有多少个客户

//客户结构体数组
struct Customer customers[100]; //最多有100个客户,超过100个,可以拓展

//显示Customer信息
void getCustomerInfo(struct Customer *c) {
printf("%d\t%s\t%c\t%d\t%s\t%s\n", (*c).id, (*c).name, (*c).gender, (*c).age,
(*c).phone, (*c).email);
}

void add() {
customers[customerNum].id = customerNum + 1; //编号按照自增规则
printf("姓名:");
scanf("%s", customers[customerNum].name);
getchar();
printf("性别:");
scanf("%c", (&customers[customerNum].gender));
getchar();
printf("年龄:");
scanf("%d", (&customers[customerNum].age));
printf("电话:");
scanf("%s", customers[customerNum].phone);
printf("邮箱:");
scanf("%s", customers[customerNum].email);
getchar();
printf("添加完成\n");
customerNum++; //自增
}

//根据输入的id,返回index,没有返回-1
int findIndex(int id) {
int index = -1;
int i;
for (i = 0; i < customerNum; i++) {
if (customers[i].id == id) {
index = i;
}
}
return index;
}

//返回int,1表示删除成功,返回0表示删除失败
//接收的是要删除的客户id
int del(int id) {
int index = findIndex(id);
if (index == -1) {
return 0;
} else {
//删除的本质就是将id编号的这个客户移除
// 1.从index+1开始,整体向左移动
for (int i = index + 1; i < customerNum; i++) {
customers[i - 1] =
customers[i]; //使用index+1位置的customer覆盖为index位置的customer
}
// 2.customerNum-1
customerNum--;
return 1;
}
}

void change(int id){
printf("姓名:");
scanf("%s", customers[id].name);
getchar();
printf("性别:");
scanf("%c", (&customers[id].gender));
getchar();
printf("年龄:");
scanf("%d", (&customers[id].age));
printf("电话:");
scanf("%s", customers[id].phone);
printf("邮箱:");
scanf("%s", customers[id].email);
getchar();
printf("更改完成\n");
}

void update() {
int id;
char c;
printf("请输入更改的客户编号(-1退出):");
scanf("%d", &id);
getchar();
if (-1 == id) {
printf("您退出了更改客户\n");
return;
}
if (findIndex(id) == -1) {
printf("您输入的id不存在\n");
return;
}
printf("查找到该用户,请开始更改信息:\n");
change(--id);
}

//显示删除客户的界面,然后调用
void delView() {
int id;
char c = ' ';
printf("请选择删除客户编号(-1退出):");
scanf("%d", &id);
getchar();
if (id == -1) {
printf("你放弃了删除\n");
return;
}
printf("确认是否删除:");
scanf("%c", &c);
getchar();
if (c == 'y') {
if (del(id) == 0) {
printf("删除失败,id不存在\n");
} else {
printf("恭喜删除成功\n");
}
}
}

//退出系统的方法
void exits() {
char c;
while (1) {
printf("您真的要退出程序吗:");
scanf("%c", &c);
getchar();
if (c == 'y' || c == 'n') {
if (c == 'y') {
printf("您退出了系统\n");
loop = 0;
return;
} else {
return;
}
} else {
printf("输入有误,请重新输入\n");
}
}
}

//显示客户信息列表
void listCustomers() {
int i = 0;
printf("----客户列表----\n");
printf("编号\t姓名\t性别\t年龄\t电话\t邮箱\n");
for (i = 0; i < customerNum; i++) { //表示客户的编号
getCustomerInfo(&customers[i]);
}
}

void mainMenu() {
do {
printf("客户信息管理软件\n");
printf("添加客户\n");
printf("修改客户\n");
printf("删除客户\n");
printf("客户列表\n");
printf("退出系统\n");
printf("请输入(1-5):");
scanf("%c", &key);
getchar();
switch (key) {
case '1':
printf("添加客户\n");
add();
break;
case '2':
printf("修改客户\n");
update();
break;
case '3':
printf("删除客户\n");
delView();
break;
case '4':
printf("客户列表\n");
listCustomers();
break;
case '5':
printf("退出系统\n");
exits();
break;
default:
printf("您输入有误,请重新输入\n");
}
} while (loop);
}

int main() {
//测试数据
customers[0].id = 1;
customers[0].age = 10;
strcpy(customers[0].email, "[email protected]");
customers[0].gender = 'f';
strcpy(customers[0].name, "tom");
strcpy(customers[0].phone, "010");
mainMenu();
return 0;
}

文件

文件是数据源(保存数据的地方)的一种,比如大家经常使用的word文档,txt文件,excel文件…都是文件.文件最主要的作用是保存数据

文件基本介绍

文件在程序中是以流的形式来操作的。

流:数据在数据源(文件)和程序(内存)之间经过的路径

输入流:数据从数据源(文件)到程序(内存)的路径

输出流:数据从程序(内存)到数据源(文件)的路径

C标准库-stdio.h 该头文件定义了三个变量类型,一些宏和各种函数来执行输入和输出,在开发过程中,可以来查询

C输入&输出

输入时,这意味着要向程序写入一些数据,可以是以文件的形式或者从命令行进行。

C语言提供了一系列内置的函数来读取给定的输入,并根据需要写入到程序中。

当我们提到输出时,这意味着要在屏幕上,打印机上或任意文件中显示一些数据。C语言提供了一系列内置的函数来输出数据到计算机屏幕上和保存数据到文本文件或二进制文件中

C语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕

标准文件 文件指针 设备
标准输入 stdin 键盘
标准输出 stdout 屏幕
标准错误 stderr 您的屏幕

文件指针是访问文件的方式,我们会讲解如何从屏幕读取值以及如何把结果输出到屏幕上

C语言中的I/O通常使用printf()和scanf()两个函数,scanf()函数用于从标准输入(键盘)读取并格式化,printf()函数发送格式化输出到标准输出(屏幕)

getchar & putchar

int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。

这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。

int putchar(void) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(){
int c;
printf("Enter a Value:");
c = getchar();//读取一个char,并返回为一个int
printf("You Entered:");
putchar(c);
printf("\n");
getchar();//过滤回车
return 0;
}

gets & puts

char *gets(char *s) 函数从stdin读取一行到所指向的缓冲区,直到一个终止符或EOF.

int puts(const char *s) 函数把字符串s和一个尾随的换行符写入到stdout.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(){
char str[100];
printf("Enter a value:");
gets(str);
printf("You entered:");
puts(str);
return 0;
}

gets函数,C语言gets函数详解 (biancheng.net)

scanf & printf

int scanf(const char *format,…) 函数从标准输入流stdin读取输入,并根据提供的format来浏览输入。

int printf(const char *formant,…) 函数把输出写入到标准输出流stdout,并根据提供的格式产生输出。

format可以是一个简单的常量字符串,但是您可以分别指定 %s,%d,%c,%f等来输出或读取字符串,整数,字符或浮点数。还有许多其他可用的格式选项。可以根据需要使用。如需了解完整的细节,可以查看这些函数的参考手册。现在让我们通过下面这个简单的示例来加深理解

您输入一个文本并按下回车键时,程序读取输入,但是要求格式要匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main(){
char str[100];
int i;

printf("Enter a Value:");
scanf("%s %d",str,&i);

printf("You Entered: %s %d",str,i);
printf("\n");
getchar();
return 0;
}

C文件读写

讲解了C语言处理的标准输入和输出设备。我们将介绍 如何创建,打开,关闭文本文件和二进制文件。

一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。C语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。本章将讲解文件管理的重要调用。

打开文件

使用fopen()函数来创建一个新的文件或者打开一个已经有的文件,这个调用会初始化类型file的一个对象,类型file包含了所有用来控制流的必要的信息。下面是这个函数调用的原型

1
FILE *fopen(const char *filename,const char *mode);

说明:在这里,filename是字符串,用来命名文件,访问模式mode的值可以是下列值中的一个

如果处理的是二进制文件,则需使用下面的访问模式:

rb wb ab rb+ r+b wb+ w+b ab+ a+b

b:二进制文件(binary)

模式 描述
r 打开一个已有的文本文件,允许读取文件
w 打开一个文本文件,允许写入文件,如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容,如果文件存在,则该会被阶段为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新的文件。在这里,您的程序会在已有的文件内容中追加内容
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

关闭文件,使用fclose()函数,函数的原型如下:

1
int fclose(FILE *fp);

如果成功关闭文件,fclose()函数返回零,如果关闭文件时发生错误,函数返回EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF是一个定义在头文件stdio.h中的常量。

C标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。

使用文件完成后(读,写),需要在程序中将文件关闭

写入文件

下面是把字符写入到流中的函数

1
int fputc(int c,FILE *fp)

说明:函数fputc()把参数c的字符值写入到fp所指向的输出流中,如果写入成功,他会返回写入的字符,如果发生错误,则会返回EOF,您可以使用下面的函数来把一个以null结尾的字符串写入到流中

1
int fputs(const char *s,FILE *fp)

说明:函数fputs()把字符串s写入到fp所指向的输出流中。如果写入成功。他会返回一个非负值,如果发生错误,则会返回EOF。您也可以使用

1
int fprintf(FILE *fp,const char *format,...) //函数来把一个字符串写入到文件中

演示案例

注意:w或者w+会把文件清空写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(){
//创建一个文件指针
FILE *fp = NULL;
//打开该文件
fp = fopen("d:/test.txt","w+");
//将内容写入到文件中
fprintf(fp,"你好,北京\n");
fputs("您好,上海",fp);
//关闭文件
fclose(fp);
printf("创建文件完成\n");
return 0;
}

读取文件

下面是从文件读取单个字符的函数

1
int fgetc(FILE *fp);

说明:fgetc() 函数从fp所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回EOF。

下面的函数从流中读取一个字符串

1
char *fgets(char *buf,int n,FILE *fp)

说明:函数fgets()从fp所指向的输入流中读取n-1个字符。它会把读取的字符串复制到缓冲区buf,并在最后追加一个null字符来终止字符串。

如果这个函数在读取最后要给字符之前就遇到一个换行符“\n”或文件的末尾EOF,则只会返回读取到的字符,包括换行符。

也可以使用

1
int fscanf(FILE *fp,const char *format)

函数来从文件中读取字符串,但是在遇到第一个空格字符时,他会停止读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

int main(){
//创建一个文件指针
FILE *fp = NULL;
//定义一个缓冲区
char buff[1024];
//打开文件
fp = fopen("d:test1.txt","r");
//方法1 读取首行
//fscanf(fp,"%s",buff);
//输出
//printf("%s\n",buff);
//方法2 读取整个文件
//循环读取fp指向的文件内容,如果读到NULL,就结束
while((fgets(buff,1024,fp)) != NULL){
printf("%s",buff);
};
fclose(fp);
return 0;
}

问题总结

C / C++ 读取文件出现乱码解决方法 | 输出到文件出现乱码_LolitaAnn的博客-CSDN博客_c语言读取文件出现乱码

为什么不能给*p赋值呢?-CSDN社区

【C语言】已定义的函数有返回值,函数调用可以作为一个函数的实参,但是不能作为形参_qxlxi的博客-CSDN博客_函数的调用可以作为一个函数的形参

typedef的用法,C语言typedef详解 (biancheng.net)

C语言中 %d 与 %i 的区别 和注意事项_默认许可人的博客-CSDN博客