(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 = "CSDN";
注意事项:除了使用赋值运算符实现两个同类型的结构变量之间的整体复制外,其他运算符均不能用于两个结构变量之间的整体运算。,也不能直接对结构变量进行整体输入\输出,只允许对结构变量各成员进行输入\输出。
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");