当前位置: 首页 > news >正文

C primer plus学习笔记 —— 7、字符串

文章目录

    • 定义字符串
      • 字符串常量
      • 字符串数组
      • 指向char的指针
        • 不同点
        • 操作常量区字符串
      • 定义字符串数组
    • 字符串IO(输入/输出)
      • 字符串输入
        • 几种字符串输入函数对比
      • 字符串输出
    • 字符串函数
      • 拼接
      • 拷贝
      • 比较
      • 格式化字符串
    • 字符串和数字之间转换
      • 数字转换成字符串
      • 字符串转换成数字
        • atoi atof atol
        • strtol strtoul strtod

定义字符串

字符串是以空字符\0结尾的char类型数组
三种方式:

  1. 字符串常量
  2. char类型数组
  3. 指向char的指针

字符串常量

"I am a string"
用双引号括起来的内容就是字符串常量。编译器会在末尾自动加空字符\0
他属于静态存储类别,也就是该字符串只会被存储一次,在整个程序生命周期内存在。
printf("s%," "sdf");使用%s在printf中打印字符串转换。

字符串数组

char words[71] = "I am a string in an array.";
char wokd[10] = {'c','g','a','/0'};
char words3[] = "I am a string in an array.";
  1. 使用字符串数组要有足够的空间存储字符串。末尾会存在空字符。
  2. 注意第二种方式是标准数组初始化形式,但是如果不加最后空字符则他只是char数组,有了空字符他才可以表示字符串。
  3. 所以我们一般采用第三种方式来让编译器自动计算数组大小。非常方便。

因为字符串数组和数组一样,所以我们也可以使用指针表示数组首元素地址

char * p = words;
p == words == &words[0]

指向char的指针

char * pt1 = "Something is pointing at me.";
char pt2[] = "Something is pointing at me.";

以上两个声明几乎相同。
pt1和pt2都是该字符串的首地址。

不同点

#define MSG "I'm special"
int main()
{
    char ar[] = MSG;
    char *pt = MSG;
    printf("address of \"I'm special\": %p \n", "I'm special"); //0x400660
    printf("          address of MSG: %p\n", MSG);              //0x400660
    printf("              address ar: %p\n", ar);               //0x7ffe88148230
    printf("              address pt: %p\n", pt);               //0x400660
    printf("address of \"I'm special\": %p \n", "I'm special");//0x400660

	//操作字符串常量区
    printf("\n");
    printf("%s \n", MSG);
    pt[2] = '4'; //segment fault
    // printf("%s", MSG);
    return 0;
}

本身字符串是存储在静态常量区,我们打印出他的地址。虽然打印了两次这个字符串,实际程序使用的都是同一个地址,也就是他在静态存储区中只存在一份。
我们发现字符指针是直接指向这个地址,而字符串数组的首地址并不指向这个地址。

也就是有:

  1. 数组形式的定义,表示在计算机分配了一个包含29个元素的数组。把静态存储区中的字符串拷贝到数组中。
  2. 字符指针是直接指向这个字符串所在静态存储区中的地址。

操作常量区字符串

如果我试图修改常量区的字符串,比如上面代码,修改字符串中某一个字符。pt[2] = '4';会在运行时报段错误。
所以建议在使用指针指向的字符串时加const防止修改字符串。

const char *pt = "I'm special";

如果要修改原字符串,则不能使用指针表示法,可以使用字符串数组表示法,这样就可以修改字符串的副本。

定义字符串数组

    const char *mytalents[LIM] = {
        "Adding numbers swiftly",
        "Multiplying accurately", "Stashing data",
        "Following instructions to the letter",
        "Understanding the C language"
    };
    char yourtalents[LIM][SLEN] = {
        "Walking in a straight line",
        "Sleeping", "Watching television",
        "Mailing letters", "Reading email"
    };

定义字符串数组,也可以使用上述两种方式。
指针方式:效率更高,因为不用存储字符串副本,但是不能修改原字符串。(绝大多数字符串操作由指针完成)
数组方式:效率低,但是可以修改字符串。

字符串IO(输入/输出)

字符串输入

在读取字符串时,scanf(%s)只能读取一个单词。而我们常常需要读取一整行输入。

gets()函数是直接读取一整行。但是他只有一个唯一的参数,无法检查空间是否能装下用户输入的字符数量。所以C99建议不适用这个函数,而C11直接将该函数废弃。

通常使用fgets()来代替gets()

#define STLEN 14
int main(void)
{
    char words[STLEN];
    puts("Enter a string, please.");
    fgets(words, STLEN, stdin); //标准输入,从键盘读取
    return 0;
}

fgets()第二个参数会限制读入字符的最大数量。会读入n-1个字符(存储一个空字符),或者遇到读到遇到的第一个换行符为止。
fgets会读入输入的换行符,比如输入 apple, 那么words中存储为apple\n\0

几种字符串输入函数对比

scanf(),gets(), gets_s(), fgets()。

  • gets() 输入太长会擦除数据
  • gets_s() 当输入不符合预期则处理不太方便
  • fgets()通常作为最佳选择
  • scanf() 会在空白字符(空行,空格,制表符或换行符)就读入停止

字符串输出

直接使用puts()函数

字符串函数

字符串函数都在string.h头文件中

strlen(char *) 返回值是字符串长度

拼接

strcat(char*, char *) 接收两个字符串,并把第二个字符串附加到第一个末尾。然后将第一个字符串修改为拼接后的字符串,第二个不变,返回第一个字符串的地址。

但是strcat有潜在危险,他不会检查第一个字符串是否能够容纳拼接后的字符串。容易造成溢出。如果第一个参数容量不够,则第一个字符串就会丢失’/0’而导致程序崩溃。

 char * a = "abcde";
    char * b = "fgh";
    strcat(a,b); //segment fault

strncat(char*, char *,int ) 在strcat的基础上,增加第三个参数,他表示能添加的最大字符串长度。strncat 函数, 多了一个参数 len ,意思是追加的字符串的 元素的个数 ,从而 避免了 字符串在 追加本身的时候 ,由于丢失‘\0’ ,而 导致的程序崩溃 。还是需要保证第一个字符串容量足够,虽然程序不会崩溃,但是会可能截断第二个参数。

拷贝

与拼接原理类似

strncpy可以保证程序不崩溃,如果

比较

在比较字符串时我们不能用==。因为双等号比较的是地址是否相等。比较字符串内容我们使用strcmp。
strcmp(char* , char*); 字符串比较函数,比较的是两个字符串的内容。而不是整个数组,即使第一个字符串数组长度比第二个大,只要内容相同。就相等。

如果两个字符串相同,则返回0. 如果第一个字符串在第二个字符串前面,则返回负数。如果第一个字符串在第二个字符串后面则返回正数。

strncmp(char*, char*, int); 第三个参数指定,比较的长度,也就是比较两个字符串的前n个字符。

格式化字符串

sprintf()该函数和printf()函数相同,只不过他是把格式化后的字符串放到字符数组中而不是显示到屏幕。
int sprintf( char *buffer, const char *format [, argument,...] );
第一个参数就是指向要写入的那个字符串的指针

int main()
{
   char str[80];
   float a = 3.14;
   sprintf(str, "Pi 的值 = %f", a);
   puts(str);
   
   return(0);
}

字符串和数字之间转换

数字可以以字符形式存储,也可以以数值形式。

比如213,他可以已字符数组2,1,3,‘0/’的形式存储,也可以以int形式直接存储。

**C语言在运算的时候只能以数值形式,但是在打印显示的时候要求以字符串形式。**比如printf中我们需要把数值使用%d转换成字符串显示出来,而scanf()是把标准输入的字符串给转换成数值保存到某个变量。

数字转换成字符串

  1. 使用sprintf()函数,如上所讲。
  2. 使用itoa,用到stdlib

字符串转换成数字

atoi atof atol


#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char* s = "3";
    int count = atoi(s);
    for (int i = 0; i < count; i++)
    {
        printf("%d \n", i);
    }
    
    return 0;
}

但是这些函数有个问题就是他只把开头的整数转成字符,比如"42rng"他会返回整数42,如果字符串中没有数字,他会返回0.这种未定义的情况可能造成不安全的问题。所以我们可以选择下面的函数。

strtol strtoul strtod

long int strtol(const char *str, char **endptr, int base) 把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base表示进制

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char str[30] = "2030300 This is test";
    char *ptr;
    long ret;

    ret = strtol(str, &ptr, 10); //第一个参数要转换的字符串,第二个参数是转换后的字符串部分,返回值是数值部分
    printf("数字(无符号长整数)是 %ld\n", ret);
    printf("字符串部分是 |%s|", ptr);
    
    return 0;
}

注意没有strtoi

相关文章:

  • 【我的OpenGL学习进阶之旅】当你运行OpenGL程序的时候,程序并不绘制任何内容,并且白屏和黑屏的时候怎么排查?
  • 【Node.js项目】大事件项目:后台架构图(含具体技术栈)、典型代码
  • Connor学Android - JNI和NDK编程
  • DOM 重点核心
  • 基于Python、wxpython的高校教务系统设计与实现
  • python的安装教程
  • DDD 洋葱架构才是 yyds!阿里大牛手记(DDD)领域驱动设计应对之道
  • Android 进阶——系统启动之SystemServer创建并启动PackageManagerService服务(十一)
  • JavaScript之document对象最常用相关知识总结
  • SpringBoot 临时属性、4种配置文件级别、自定义配置文件
  • 分布式事务(五)———可靠消息队列解决方案
  • Spring IOC概念与实现(注解方式)
  • springboot系列(二十四):如何实现Excel文件导出?这你得会 | 超级详细,建议收藏
  • 【C++】类和对象之六大默认成员函数上
  • 我在github上读清华北大|浙大计算机课程共享项目
  • .pyc 想到的一些问题
  • [NodeJS] 关于Buffer
  • ➹使用webpack配置多页面应用(MPA)
  • angular组件开发
  • go语言学习初探(一)
  • HTTP那些事
  • Java读取Properties文件的六种方法
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Lsb图片隐写
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • 区块链共识机制优缺点对比都是什么
  • 入门级的git使用指北
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 我感觉这是史上最牛的防sql注入方法类
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • "无招胜有招"nbsp;史上最全的互…
  • #if #elif #endif
  • (10)ATF MMU转换表
  • (42)STM32——LCD显示屏实验笔记
  • (C语言)二分查找 超详细
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (java)关于Thread的挂起和恢复
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (多级缓存)多级缓存
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (九十四)函数和二维数组
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转载)深入super,看Python如何解决钻石继承难题
  • (转载)虚函数剖析
  • ****** 二 ******、软设笔记【数据结构】-KMP算法、树、二叉树
  • ****Linux下Mysql的安装和配置
  • *上位机的定义
  • .[backups@airmail.cc].faust勒索病毒的最新威胁:如何恢复您的数据?
  • .Net - 类的介绍
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .Net 高效开发之不可错过的实用工具
  • .Net6 Api Swagger配置