c++与c的字符串类型数据结构探究

前言

一直没有搞得很明白 c++字符串和 c的字符数组

今天写一篇文章反省一下


c++的string

c++提供了string模版类

不需要包含特定的头文件 

内置好的数据结构string,直接使用即可

我们经常使用的

string str;

这样的代码其实是实例了一个string对象,叫做str;

0.初始化

参考:C++11中变量初始化方法汇总_c++11类成员变量定义的时候初始化-CSDN博客

 使用 “ = ” 进行的是拷贝初始化

string s1;       // 默认初始化为空字符串
string s2 = s1;  // 拷贝初始化,s2是s1的副本

直接初始化

string s1();        // 直接初始化为空字符串 
string s2("hi");    // 直接初始化
string s3(3, 'c');  // 直接初始化,s2的内容是ccc

以及string类具有很多的成员函数可以方便我们调用

1.获取字符串长度

str.length();//获取str字符串长度
str.size();//二者等价    但是个人用size()习惯在vector向量这种数据结构中

2.字符串的读入

cin>>s;//不能读入空格! 以空格,制表符,回车符作为结束标志
getline(cin,s);//可以读入空格和制表符 以回车符作为结束的标志
#include<iostream> 

using namespace std;

int main(){
	string s;
	cin>>s;//读入遇到空格,制表符,回车键结束读入
	int n = s.length();
	cout<<n<<' '<<s;
	return 0;
}

#include<iostream> 

using namespace std;

int main(){
	string s;
	getline(cin,s);//读入包含空格和制表符,以回车键结束
	int n = s.length();
	cout<<n<<' '<<s;
	return 0;
}

3.string字符串中元素(字符)访问

//1.直接下标访问
    s[0];
//2.使用at()函数    括号内填写字符下标
    s.at(4);
//3.使用for()循环遍历访问
    for(int i=0;i<n;i++){
        cout<<s[i]<<' ';
    }
#include<iostream> 

using namespace std;

int main(){
	string s;
	cin>>s;
	int n = s.length();
	cout<<n<<' '<<s[0]<<endl;
	cout<<s.at(4)<<endl;
    for(int i=0;i<n;i++)
		cout<<s[i]<<' ';
	return 0;
}

4.末尾添加字符

c++中,对于 “=” 和 “+” 进行了重载;

其中 “+” 字符约等于vector中的push_back()函数,在末尾添加字符

在《剑指OFFER》配套书中有一道题:替换空格

可以很明了地阐释使用该重载方法的便捷

#include <cstring>
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @return string字符串
     */
    string  str = "%20";

    string replaceSpace(string s) {
        // write code here
        string res = "";
        int n = s.length();
        for(int i=0;i<n;i++){
            if(s[i]!=' '){
                res+=s[i];
            }
            else{
                res+=str;
            }
        }
        return res;
    }
};

 重载 “+” 理解起来很容易,但是还有一个函数可以实现上述功能:

        append()函数

注意: 括号内需使用 " " //双引号 引号内填写加入的内容 该内容加在末尾

#include<iostream> 
#include<cstring> 

using namespace std;

int main(){
	string s;
	cin>>s;
	s.append("a");//加入字符a
	int n = s.length();
	cout<<n<<' '<<s[0]<<endl;
	cout<<s.at(4)<<endl;
	for(int i=0;i<n;i++)
		cout<<s[i]<<' ';
	return 0;
}

    s.append("alovelygirl");
    s+="alovelygirl";

其他函数

可以参考这篇文章啦(感觉其他也不太常用,我一般都是用for遍历之后再对元素操作的)

C++的string库用法总结 - 知乎 (zhihu.com)

补充一下

在《剑指OFFER》配套书中有一道题:数字序列中某一位的数字

很好地利用了字符串的下标取数,然后类型转换返回int

题目如下:

#include <string>
#include <vector>
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 
     * @return int整型
     */
    int findNthDigit(int n) {
        // write code here
        //数学问题
        int weishu = 1;
        long long start =1;
        long long sum =9;
        while (n>sum) {
            n-=sum;
            start*=10;
            weishu++;
            sum = 9*start*weishu;
        }
        int num = start+(n-1)/weishu;
        int index = (n-1)%weishu;
        return to_string(num)[index]-'0';
    }
};

注意最后一行的 将下标找到之后 将num转为字符串格式,取对应下标的值,再将其转为int类型(-‘0’操作)返回,即为所求


c的字符数组

参考:深入 理解char * ,char ** ,char a[ ] ,char *a[] 的区别 - 知乎 (zhihu.com)

众所周知,c中没有string类的概念

但是具有 字符 char 这样的数据结构

思考一下,把地址连续的字符char串起来不就是字符串了吗

很自然会想到 字符+数组(概念的结合)=字符数组

char[ ]

(由于C语言中没有真正的string字符串类型:

所以通过字符数组表示字符串,因为它的元素地址是连续的)

参考翁恺老师的c语言课程,在这里补充一下:

c中的字符串,结尾是有 '\0' 或者 0 存在的(但是长度计算时不包含)

一般我们强调 '\0' 因为其大小固定为1字节(0可能有歧义int为4字节)

区分:'0' 为字符0,ASC码值为0x30(48);

这个是c中字符数组与字符串的根本区别(结尾为 '\0'

char str[10] = "hello";

先来看看数组的概念:

C语言中规定数组代表数组所在内存位置的首地址

C语言中操作字符串是通过它在内存中的存储单元的首地址进行的,这是字符串的终极本质

便于理解以下放代码:

ps:可以自己尝试

说明:“%p”打印变量地址

若需要实践,将第一步你的电脑输出的值复制到第二行的地址位置,取地址输出;

#include <stdio.h>

int main(){
	char s[6]= "hello";
	printf("%p\n",a);
	printf("%s\n",0x000000000062FE10);
	return 0;
}

足以见得,

我们把hello 看作是字符串,但是编译器把它看作是地址 0x62FE10

即字符串常量的本质是代表它的第一个字符的地址

s = 0x62FE10

这样写似乎更符合本质含义;

搞清楚这个问题后,那么printf("%s\n",s);它的原理其实也是通过字符串首地址输出字符串;

传给它的其实是s所保存的字符串的地址;(以上代码多多理解一下,也有助于对指针的理解)

char*

字符指针

#include <stdio.h>

int main(){
	char s[6]= "hello";
	char* a = s;
	printf("%p\n",a);
	printf("%s\n",a);
	printf("%s\n",0x00000000006FFE10);
	return 0;
}

 这里的char *a表示保存字符串首地址的指针变量

二者区别

char *a 只是一个保存字符串首地址的指针变量;

char s[ ] 是许多连续的内存单元,单元中的元素为char ;

之所以用 char* a 能达到char s[ ] 的效果,还是c中字符串的本质:首地址;

即有一个字符串地址,便可以随心所欲的操作。


但是,char* a 和 char a[ ] 的本质属性是不一样的:

char* a 的使用

细细分析

先看代码:

#include<stdio.h> 

int main(){

	const char *str= "helloyxy";
	printf("%s %p %p\n",str,str,&str);
	
	const char *str2 = "hellosh";
	str = str2;
	printf("%s %p %p\n",str,str,&str);

	return 0;
}

可以看到字符指针本质是指针(指针变量),其中存放的是地址,但是变量本身也是有地址的

在这里我们可以看到,指针(指针变量)的地址是不变的那个量,即0x6ffdf8(右边的)

指针变量所指向的是字符串的首地址(指针变量内保存字符串的首地址):

分别是 “helloyxy”:0x488001

             “hellosh”:0x488014

仅仅改变了指针的指向(改变了指针变量内保存字符串的首地址的值)   : 从 “helloyxy”字符串首地址(0x488001) 变到了 “hellosh”字符串首地址 (0x488014)。

那我们能不能直接改变str所指向的值的内容,实现输出 “hellosh” 的效果呢?

再看代码:

#include<stdio.h>
int main(){

	const char *str= "helloyxy";
	
	scanf("%s",str); 
	printf("%s %p %p\n",str,str,&str);

	return 0;
}

程序强制退出了,为什么呢:

因为指针指向的 “helloyxy” 字符串首地址是存储在 常量区 的 该区域内的值定义后无法被改变,即此处用户键盘输入的值是非法操作,程序就会异常退出;

但是我们可以通过上述操作改变指针的指向,即可改变str存放的字符串的首地址(即我们意义上的 改变str存放的字符串)

char a[ ]的使用

#include<stdio.h>

int main(){
	char s[10]="123";
	printf("%s %p %p\n",s,s,&s);
	
	scanf("%s",s); 
	int n = strlen(s);
	printf("%s %p %p\n",s,s,&s);
	
	return 0;
}

 

可以看到此处我们可以直接通过键盘输入改变s字符串内容(但是字符串首地址未改变),说明该字符数组char s[]存在于栈区

(栈区就像是一家客栈,里面有很多房间,客人来了之后自动分配房间,房间里的客人可以变动,是一种动态的数据变动)

可以理解为将“123”请出房间,让“hellosh”住入 0x71fe10房间;

栈区域的内容可以改变,即可以通过键盘输入改变字符串s内容。

最后看看不同它们大小的不同

上代码

#include <stdio.h>

int main(){

	char s[6]= "hello";
	char* a = s;
	printf("%p\n",s);//输出字符数组s的首地址 
	printf("%p\n",a);//输出字符指针a所指向的地址(即a存放字符串的首地址) 
    printf("%p\n",&a);//输出字符指针a本身的地址
	printf("%s\n",a);//输出a存放的字符串
	printf("%s\n",0x000000000062FE10);//输出0x62FE10地址存放的字符串
	printf("%d\n",sizeof(a));//输出指针的大小 64位计算机 指针大小为 8byte 
	printf("%d\n",sizeof(s));//输出字符串大小 6byte 

	return 0;
}

  • 字符数组 char s[6] 的大小取决于我们代码中在中括号[ n ]内输入的整数大小

        (n byte 以字节为单位);

  • 字符指针 char* a 的大小取决于计算机,64位计算机 指针大小为8 byte,32位为4 byte。

 str函数

头文件:#include <string.h>

最后的最后说一下c中内置的str函数,可以很方便地处理字符数组&字符指针

strlen(s)

字符串有效长度函数

返回值:整型
功能:用来测量一个字符串的有效长度

所谓有效,就是从字符串中的第一个字符开始,一直到第一个空字符’\0’为止(不包括这个空字符)所占的字节数;

#include <stdio.h>
#include <string.h>
int main(){
	char s[10]= "hello\0yxy";//长度计入 遇到 \0 结束 

	printf("%d\n",strlen(s));//有效长度 
	return 0;
}

strcat(s,s2)

字符串拼接 s2连接在s后面,返回s地址 
功能:将字符串s2的有效字符拼接在字符串s后,并且拼接时字符串s的结束标志被字符串s2的第一个字符替代

返回值:字符指针(s地址)
注意:拼接函数执行完毕后,字符串s2并没有改变,字符串s改变,最后字符串s等于原字符串s+字符串s2;

#include <stdio.h>
#include <string.h>
int main(){
	char s[10]= "hello";
	char s2[10]= "yxy" ;

	printf("%s\n",strcat(s,s2));//字符串拼接 s2连接在s后面,返回s地址 
	return 0;
}

strcpy(s,s2)

复制字符串函数
功能:将字符串s2内的字符复制并覆盖到字符串s;
返回值:字符指针(s地址)
含义:将字符串s2的字符,全部把字符串s的内容给覆盖(包括结束标志和空字符)
注意:
1. 字符串s的定义空间也必须要大于加上字符串s2存储空间;不然程序可能报错或者出现潜在错误;
2. 复制函数执行完毕后,原字符串s的有效字符就被字符串s2全部替换掉,并且执行后的字符串s的有效字符串等于字符串s2;

#include <stdio.h>
#include <string.h>
int main(){
	char s[10]= "hello";
	char s2[10]= "yxy";
	printf("%s\n",strcpy(s,s2));//覆盖 
	return 0;
}

strcmp(s,s2)

字符串比较函数

返回值:
1,说明字符串1>字符串2;
0,说明字符串1==字符串2;
-1,说明字符串1<字符串2;


比较的方式:将字符串1和字符串2中下标相同的字符从0下标开始逐个比较;
比较的依据:ASCII表;(符号在表中的编码越大,字符就越大)
注意:函数执行完毕后两个字符串并没有发生改变;

#include <stdio.h>
#include <string.h>
int main(){
	char s[10]= "hello";
	char s2[10]= "yxy" ;

	printf("%d\n",strcmp(s,s2));//比较字符串
	return 0;
}

参考:

​​​​​​C语言字符串头文件string.h中的函数_字符头文件-CSDN博客(适合了解用法)

C语言str函数系列-CSDN博客(适合了解函数本质-如何实现)


结束语

就是这些啦,真的感觉很多细节需要注意

所以我们为了不搞混,可以熟悉使用一种即可

(一般建议使用c++啦,毕竟面向对象真的很方便而且重要)

  • 50
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值