前言
一直没有搞得很明白 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++啦,毕竟面向对象真的很方便而且重要)