2.9、字符数组、字符串-程序员宅基地

技术标签: C 语言入门  算法  c语言  C 语言入门与进阶  数据结构  开发语言  

1、字符串数组与字符串

  • 用来存放字符的数组称为字符数组
char a[10];     // 一维字符数组
char b[5][10];  // 二维字符数组
char c[20]={'c', '  ', 'p', 'r', 'o', 'g', 'r', 'a','m'};  // 给部分数组元素赋值
char d[]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm' };   // 对全体元素赋值时可以省去长度
  • 字符数组实际上是一系列字符的集合,即字符串(String)
  • C 语言中没有专门的字符串变量,没有 string 类型,通常用一个字符数组来存放一个字符串。
  • C 语言规定,可以将字符串直接赋值给字符数组:
char str[30] = {"www.yuque.com/it-coach"};
// 这种形式更加简洁, 实际开发中常用
char str[30] = "www.yuque.com/it-coach";  
  • 数组第 0 个元素为'c',第 1 个元素为'.',第 2 个元素为'b',后面的元素以此类推。
  • 为了方便,也可以不指定数组长度:
char str[] = {"www.yuque.com/it-coach"};
char str[] = "www.yuque.com/it-coach";  
  • 给字符数组赋值时通常将字符串一次性地赋值(可以指明数组长度,也可以不指明),而不是一个字符一个字符地赋值。
  • 注意:字符数组只有在定义时才能将整个字符串一次性赋值给它,一旦定义完了,就只能一个字符一个字符地赋值:
char str[7];

// 错误
str = "abc123";  
// 正确
str[0] = 'a'; str[1] = 'b'; str[2] = 'c';
str[3] = '1'; str[4] = '2'; str[5] = '3';

(1)字符串结束标志【IMP】

  • 字符串是一系列连续的字符的组合,在内存中定位一个字符串除了要知道它的开头,还要知道它的结尾。找到字符串的开头很容易,知道它的名字(字符数组名或字符串名)就可以;然而,如何找到字符串的结尾?
  • C 语言中,字符串总是以'\0'作为结尾,所以'\0'也被称为字符串结束标志(字符串结束符)。
    • '\0'是 ASCII 码表中的第 0 个字符,英文称为 NUL,中文称为 “空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在 C 语言中唯一的作用就是作为字符串结束标志。
  • C 语言在处理字符串时,会从前往后逐个扫描字符,一旦遇到'\0'就认为到达了字符串的末尾,就结束处理。'\0'至关重要,没有'\0'就意味着永远也到达不了字符串的结尾。
  • " "包围的字符串会自动在末尾添加'\0'。例如"abc123"从表面看只包含了 6 个字符,其实 C 语言会在最后隐式地添加一个'\0',这个过程是在后台默默地进行的,所以我们感受不到。
  • 下图演示了"C program"在内存中的存储情形:

  • 注意:逐个字符地给数组赋值并不会自动添加'\0'
    • char str[] = {'a', 'b', 'c'};
    • 数组 str 的长度为 3,而不是 4,因为最后没有'\0'
  • 当用字符数组存储字符串时,要特别注意'\0'(要为'\0'留个位置);这意味着,字符数组的长度至少要比字符串的长度大 1:
    • char str[7] = "abc123";
    • "abc123"看起来只包含了 6 个字符,却将 str 的长度定义为 7,就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。
    • 当字符串长度大于数组长度时,有些较老或者不严格的编译器并不会报错,甚至连警告都没有,这就为以后的错误埋下了伏笔,要多多注意。
  • 有时程序的逻辑要求必须逐个字符地为数组赋值,这时就很容易遗忘字符串结束标志'\0'
  • 示例:将 26 个大写英文字符存入字符数组,并以字符串的形式输出
#include <stdio.h>
int main(){
    char str[30];
    char c;
    int i;
    for(c=65,i=0; c<=90; c++,i++){
        str[i] = c;
    }
    printf("%s\n", str);
    return 0;
}

/*
ABCDEFGHIJKLMNOPQRSTUVWXYZ@
*/
  • 大写字母在 ASCII 码表中是连续排布的,编码值从 65 开始,到 90 结束,使用循环非常方便。
  • 在很多编译器下,局部变量的初始值是随机的垃圾值,而不是通常认为的 “零” 值。
  • 局部数组(在函数内部定义的数组)也有这个问题,很多编译器并不会把局部数组的内存都初始化为 “零” 值,而是放任不管,所以它们的值也是没有意义的垃圾值。
  • 在函数内部定义的变量、数组、结构体、共用体等都称为局部数据。很多编译器下局部数据的初始值都是随机的、无意义的,而不是通常认为的 “零” 值。
  • 本例中的 str 数组在定义完成以后并没有立即初始化,所以它所包含的元素的值都是随机的,只有很小的概率会是 “零” 值。循环结束以后,str 的前 26 个元素被赋值了,剩下的 4 个元素的值依然是随机的。
  • printf() 输出字符串时,会从第 0 个元素开始往后检索,直到遇见'\0'才停止,然后把'\0'前面的字符全部输出,这就是 printf() 输出字符串的原理。
  • 本例中使用 printf() 输出 str,按理到了第 26 个元素就能检索到'\0',就到达了字符串的末尾,然而事实却不是这样,由于并未对最后 4 个元素赋值,所以第 26 个元素不是'\0',第 27 个也不是,第 28 个也不是……可能到了第 50 个元素才遇到'\0',printf() 把这 50 个字符全部输出出来,就是上面的样子,多出来的字符毫无意义,甚至不能显示。
  • 数组总共才 30 个元素,到了第 50 个元素不早就超出数组范围了吗?是的,的确超出范围了!然而,数组后面依然有其它的数据,printf() 也会将这些数据作为字符串输出。
  • 不注意'\0'的后果很严重:不但不能正确处理字符串,甚至还会毁坏其它数据。
  • 要想避免这些问题也很容易,在字符串的最后手动添加'\0'即可。
  • 修改上面的代码,在循环结束后添加'\0'
#include <stdio.h>
int main(){
    char str[30];
    char c;
    int i;
    for(c=65,i=0; c<=90; c++,i++){
        str[i] = c;
    }
    // 此处为添加的代码, 也可以写作 str[i] = '\0'; 根据 ASCII 码表, 字符'\0'的编码值就是 0
    str[i] = 0;  
    printf("%s\n", str);
   
    return 0;
}

/*
ABCDEFGHIJKLMNOPQRSTUVWXYZ
*/
  • 但这样的写法貌似有点业余(不够简洁),更加专业的做法是将数组的所有元素都初始化为 “零”值,这样才能够从根本上避免问题:
#include <stdio.h>
int main(){
    // 将所有元素都初始化为 0, 或者说 '\0'
    char str[30] = {0};  
    char c;
    int i;
    for(c=65,i=0; c<=90; c++,i++){
        str[i] = c;
    }
    printf("%s\n", str);
   
    return 0;
}

/*
ABCDEFGHIJKLMNOPQRSTUVWXYZ
*/
  • 如果只初始化部分数组元素,那么剩余的数组元素也会自动初始化为 “零” 值,所以只需要将 str 的第 0 个元素赋值为 0,剩下的元素就都是 0 了。

(2)字符串长度

  • 字符串长度:字符串包含了多少个字符(不包括最后的结束符'\0')。例如"abc"的长度是 3,而不是 4。
  • C 语言中使用string.h头文件中的 strlen() 函数来求字符串的长度:
    • length strlen(strname);
      • strname:字符串或字符数组的名字
      • length:使用 strlen() 后得到的字符串长度,是一个整数
#include <stdio.h>
#include <string.h>  // 记得引入该头文件
int main(){
    char str[] = "http://www.yuque.com/it-coach/";
    long len = strlen(str);
    printf("The lenth of the string is %ld.\n", len);
   
    return 0;
}

/*
The lenth of the string is 30.
*/

2、字符串的输入和输出

(1)字符串的输出

  • C 语言中有两个函数可以在控制台(显示器)上输出字符串:
    • puts():输出字符串并自动换行,该函数只能输出字符串。
    • printf():通过格式控制符%s输出字符串,不能自动换行。printf() 还能输出其他类型的数据。
#include <stdio.h>
int main(){
    char str[] = "www.yuque.com/it-coach";
    printf("%s\n", str);  // 通过字符串名字输出
    printf("%s\n", "www.yuque.com/it-coach");  // 直接输出
    puts(str);            // 通过字符串名字输出
    puts("www.yuque.com/it-coach");            // 直接输出
    return 0;
}

/*
www.yuque.com/it-coach
www.yuque.com/it-coach
www.yuque.com/it-coach
www.yuque.com/it-coach
*/
  • 注意:输出字符串时只需要给出名字,不能带后边的[ ]
// 下面的两种写法都是错误的
printf("%s\n", str[]);
puts(str[10]);

(2)字符串的输入

  • C 语言中有两个函数可以让用户从键盘上输入字符串:
    • scanf():通过格式控制符%s输入字符串。scanf() 还能输入其他类型的数据。
    • gets():直接输入字符串,并且只能输入字符串。
  • scanf()gets() 的区别:
    • scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
      • 其实 scanf() 也可以读取带空格的字符串,完全可以替代 gets()。scanf() 的用法还可以更加复杂和灵活,比 gets() 的功能更加强大。比如,以下功能都是 gets() 不具备的:
        • 控制读取字符的数目
        • 只读取指定的字符
        • 不读取某些字符
        • 把读取到的字符丢弃
    • gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。即 gets() 用来读取一整行字符串。
#include <stdio.h>
int main(){
    char str1[30] = {0};
    char str2[30] = {0};
    char str3[30] = {0};
    // gets() 用法
    printf("Input a string: ");
    gets(str1);
    // scanf() 用法
    printf("Input a string: ");
    scanf("%s", str2);
    scanf("%s", str3);
   
    printf("\nstr1: %s\n", str1);
    printf("str2: %s\n", str2);
    printf("str3: %s\n", str3);
    return 0;
}

/*
Input a string: C C++ Java Python
Input a string: PHP JavaScript

str1: C C++ Java Python
str2: PHP
str3: JavaScript
*/
  • 第一次输入的字符串被 gets() 全部读取,并存入 str1 中。
  • 第二次输入的字符串,前半部分被第一个 scanf() 读取并存入 str2 中,后半部分被第二个 scanf() 读取并存入 str3 中。
  • 注意:scanf() 在读取数据时需要的是数据的地址,所以对于 int、char、float 等类型的变量都要在前边添加&以获取它们的地址。但是在本段代码中只给出了字符串的名字,却没有在前边添加&,因为字符串名字或者数组名字在使用的过程中一般都会转换为地址,所以再添加&就是多此一举,甚至会导致错误了。
  • int、char、float 等类型的变量用于 scanf() 时都要在前面添加&,而数组或者字符串用于 scanf() 时不用添加&,它们本身就会转换为地址。

3、字符串处理函数

  • C 语言提供了丰富的字符串处理函数,可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作。
  • 用于输入输出的字符串函数(printfputsscanfgets等)使用时要包含头文件stdio.h,而使用其它字符串函数要包含头文件string.h
  • string.h是一个专门用来处理字符串的头文件,它包含了很多字符串处理函数。

(1)strcat():字符串连接

  • strcat(string catenate):把两个字符串拼接在一起,语法格式:
    • strcat(arrayName1, arrayName2);
      • arrayName1arrayName2:需要拼接的字符串。strcat() 将把 arrayName2 连接到 arrayName1 后面,并删除原来 arrayName1 最后的结束标志'\0'。这意味着,arrayName1 必须足够长,要能够同时容纳 arrayName1 和 arrayName2,否则会越界(超出范围)。
    • strcat() 的返回值为 arrayName1 的地址。
#include <stdio.h>
#include <string.h>
int main(){
    char str1[100]="The URL is ";
    char str2[60];
    printf("Input a URL: ");
    gets(str2);
    strcat(str1, str2);
    puts(str1);
   
    return 0;
}

/*
Input a URL: www.yuque.com/it-coach
The URL is www.yuque.com/it-coach/
*/

(2)strcpy():字符串复制

  • strcpy(string copy):字符串复制,将字符串从一个地方复制到另外一个地方,语法格式:
    • strcpy(arrayName1, arrayName2);
    • strcpy() 会把 arrayName2 中的字符串拷贝到 arrayName1 中,字符串结束标志'\0'也一同拷贝。
    • strcpy() 要求 arrayName1 要有足够的长度,否则不能全部装入所拷贝的字符串。
#include <stdio.h>
#include <string.h>
int main(){
    char str1[50] = "《C语言》";
    char str2[50] = "www.yuque.com/it-coach";
    strcpy(str1, str2);
    printf("str1: %s\n", str1);
    return 0;
}

/*
str1: www.yuque.com/it-coach
*/

(3)strcmp():字符串比较

  • strcmp(string compare):字符串比较,语法格式为:
    • strcmp(arrayName1, arrayName2);
      • arrayName1arrayName2 是需要比较的两个字符串。
    • 返回值
      • 若 arrayName1 和 arrayName2 相同,则返回 0
      • 若 arrayName1 大于 arrayName2,则返回大于 0 的值
      • 若 arrayName1 小于 arrayName2,则返回小于 0 的值
  • 字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。strcmp() 从两个字符串的第 0 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或到字符串的末尾。
#include <stdio.h>
#include <string.h>
int main(){
    char a[] = "aBcDeF";
    char b[] = "AbCdEf";
    char c[] = "aacdef";
    char d[] = "aBcDeF";
    printf("a VS b: %d\n", strcmp(a, b));
    printf("a VS c: %d\n", strcmp(a, c));
    printf("a VS d: %d\n", strcmp(a, d));
   
    return 0;
}

/*
a VS b: 32
a VS c: -31
a VS d: 0
*/

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_66491750/article/details/137853753

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue

推荐文章

热门文章

相关标签