C了个++:03 - C++的字符串与 string 类

来源:互联网 发布:python 键盘控制程序 编辑:程序博客网 时间:2024/05/16 06:24

简单梳理总结了C++中字符和字符串的知识


简单基础知识部分

  • C++数据类型有3种级别:基本级类型(整型、浮点型)、复合级类型(数组、字符串、指针、地址)、抽象级类型(结构、类)。
  • 这里跟字符和字符串相关的是:char ch(基本类型)、char ar[]或char* ar(复合类型)、string str(抽象类型),另外这里不讨论wchar_t类型,字符串提供了一种存储文本信息和输出文本信息的便捷方式,因为它是存储在的连续字节的一系列字符。


基本类型char ch

  • char类型是一种特殊的整型,专为存储字符而设计的,它是8位二进制数(一个字节),存储的是128(2^7)个基本字符(包括字母和数字、标点符号等)编码值(以ASCII码为例)区别于其他整型或浮点型存储数字的方式(它们二进制的存储方式和数字的大小相关联,比如“1”对应二进制“0001”),char类型的二进制存储数字的编码值,并且和数字大小本身没有关系(比如“1”对应char类型的二进制数“0x31”)。
  • 因为char是整型(比short类型更小的整型),所以char变量可以比较大小也可以进行加减操作。字符的字面值(字符常量)就是相应字符的编码值(该值是整数常量)单引号运算符(字符值符)加单个字符得到该字符的编码值(字符常量)。一个char类型变量的值也是一个相应字符的编码值双引号运算符(字符串分割符)加字符串得到该字符串的编码值序列的首地址(并在该字符串-编码值序列的末尾自动添加NULL(空字符)的编码值,这个带地址和NULL字符的编码值序列也叫字符串常量或字符串字面值)
  • (例如在ASCII码中,字符A,该字符的  字面值==字符常量==编码值=='A'==65==0x41==0101==01000001;而对于字符变量char ch,如果ch==65/69,那么ch变量存储的是字符A/E的编码值)
  • C++将字符进行编码并用整数表示,使得操作字符值更容易,至于实现使用哪种编码和编译器(C++编译器)没关系,和系统有关系,一般windows系统使用的是ASCII码。另外,国际Unicode字符集有更多的字符(多于128个)。
  • 重要或特殊的字符的编码值及其转义序列:振铃\a\7、换行endl\n\10、回车\r\13、双引号\"\34、单引号\'\39、反斜杠\\\92、问号?\?\63、tab\t\9、退格\b\8、NULL(ctrl+@)\0、空格\32、0\48、@\64、A\65、a\97、del\127,转义序列与对应字符等价,对转义序列使用单引号运算符得到相应字符常量,应尽量使用符号转义序列不使用数字转义序列,因为数字转移序列依赖于系统的编码方式(如ASCII码)。
  • 成员函数ostream & put(char);或<<运算符打印一个字符。成员函数istream & get(char);或int get(void);或>>运算符读取一个字符。


复合类型char ar[]

  • C-风格字符串char ar[],字符串是存储在内存的连续字节中的一系列字符,NULL对于C-字符串至关重要,因为它用来标记字符串的结尾,char数组是否是char字符串的唯一要求是末尾是否有NULL字符。字符串常量或字符串字面值(即双引号格式)可以自动在字符串结尾处隐式地添加NULL字符。注意,要求字符串数组的长度>=有效字符串长度+1。另外,通过键盘输入到char数组的字符串也会自动在末尾添加NULL字符。
  • 单引号运算符(字符常量)和双引号运算符(字符串常量)的主要区别是,单引号只是对应一个字符的编码值并可以将其传递给char变量双引号对应末尾添加了一个NULL字符的一串字符的编码值序列,并且双引号传递给char*或char[]变量的是一个指向char类型字符串的首地址(注意,char[]地址的内存长度是确定的,就是数组占的字节长度,但char*的内存长度就4字节的地址长度不变,见sizeof()),并非传递字符串的编码值序列。双引号运算符(字符串常量)属于C-风格字符串。记住,数组名是数组的首地址,同样,字符串常量也是首地址。
  • 概念区分:字符常量是属于char类型的常量,它可以赋值给char类型变量ch,ch存储的内容是其字符的编码值;字符串常量是属于char*或char[]类型的常量,它是将字符串的首地址赋值给char*或char[]类型变量ar,则ar存储的内容是字符串首地址,而此地址存储的内容是一串字符的编码值序列的第一个编码值。不过ar是数组名,变量名ar通过索引运算符[]可以访问字符串数组中的特定位置的字符,即ar[i]相当于是个char类型的变量,它对应字符串中第i+1个字符的字符常量,也就是说可以通过字符常量给ar[i]赋值来修改ar字符串中第i+1个的内容。另外,也可以用 *(ar++/ar+i) 的方法访问ar字符串中的内容。
  • C-风格char字符串有三种常用操作,将char数组初始化为字符串常量、通过键盘或文件读入赋值到char数组中、通过索引运算符修改字符串中特定字符。注意,字符串常量或初始化列表({ })对char数组的赋值操作只限于初始化,因为每次双引号传递的是字符串首地址,数组名(数组首地址)在初始化过程完成赋值(或在数组变量声明时默认赋值),并在后期是不可修改的。再注意,有些实现对数组(包括char数组)的初始化操作(字符串常量或{ })需要使用static关键字,static使该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。char数组声明或初始化,地址是分配的(已赋值)长度是确定的,不存在char ar[];的声明(函数参数除外),但有char ar[]="...";的初始化。char数组在使用字符串常量初始化时,会自动用NULL字符将char数组中字符串常量的字符长度+1(strlen("...")+1)之外多余的空间填满,然而,未初始化的数组的内容是未定义的(即是以前的内容)。
  • strlen(char*ar);(头文件cstring)会寻找NULL字符进行统计ar字符串的长度,只计算可见的字符而不把空字符计算在内,单位为个字符;sizeof(ar);指出整个数组ar(其实是指针)的内存空间的长度,单位为个字节。注意sizeof();的使用针对char*指针变量和char[]数组变量是有区别的,指针的sizeof()始终是4个字节的长度(取决于系统),而数组名的sizeof()是整个数组的内存空间的长度,尽管数组变量名从功能上来讲就是一个指针,但严格来说并不是真正的指针类型。
  • 创建一个字符串的副本可以使用ps=new char(strlen(ar)+1);strcpy(ps,ar);。char数组的内容是可以通过键盘或文件输入或strcpy()或strncpy()重新写入或修改的。注意,char*指针引导的字符串不能重新写入。
  • 字符串的输入,除了cin>>方法,常用的两个方法是getline和get(两个istream类的成员函数)。cin.getline(char*ar,int+1,char);只能在分隔符之前输入int个字符,多出的1是为了添加NULL字符来保证是char数组字符串,所以要求int+1<=siziof(ar),这里的ar是数组名,前面getline参数中的ar是一个指针(地址),意思是说getline接收一个指向char类型的地址,只不过这里的地址是由数组名ar提供的。另外,getline读取并丢弃分隔符。cin.get(char*ar,int+1,char).get(ch);这个方法和getline的功能是一致的,不过相比于getline它的好处是,可以用get(ch)来查看是因为分隔符停止输入的还是因为数组已满停止输入的(因为get(char*ar,int+1,char)不读取分隔符),并且不会像getline因为数组已满设置failbit关闭输入。但对于空行,get会设置failbit,getline不会。清除failbit恢复输入使用cin.clear();。
  • 符串的输出,常用cout<<方法。只要给cout提供一个char的地址(不管它是字符串常量还是数组名还是指针),它就从该字符开始打印,直到遇到NULL字符为止。
  • 其它,任何两个由空白(空格、制表符和换行符)分割的字符串常量(双引号)都将自动拼接成一个,并且会自动去掉拼接处的NULL字符。C-风格字符串的的复制和附加,char*strcpy(ar1,ar2);  char*strncpy(ar1,ar2,n);char*strcat(ar1,ar2);  char*strncat(ar1,ar2,n);(头文件cstring),(=赋值运算符只用于初始化,不能用于后期的赋值)。调用拼接(cin>>int).get(); 要小心,输入一个int数据是后边还有个换行符的空白留在了输入队列里了。



抽象类型string str

  • C++的string类库(头文件string,注意不是头文件cstring),位于名称空间std中,实现了将字符串作为一种数据类型(类,抽象数据类型),处理string对象(变量)使处理字符串像处理基本类型变量(如char ch)一样简单方便,例如赋值或重新赋值,并且此时string对象内存储的字符串的长度可以自动调整(最大长度限制string::npos,通常是unsigned int的最大值)。未被初始化的string对象的字符串的长度自动设置为0。从理论上讲,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类对象(变量)是一个表示字符串的实体。双引号运算符(字符串常量)属于C-风格字符串,但也可以看作一个字符串的实体(一个未命名的string对象)。
  • 字符串常量(双引号形式)在char数组中仅限于初始化使用,而它对于string对象没有限制,所以可以利用字符串常量重新赋值=给已存在的string对象(类似给基本变量重新赋值=);两个char数组不能相互赋值,而两个string对象可以(类似两个基本变量的赋值传递=),对应于C-风格字符串中的常规函数char*strcpy(ar1,ar2);  char*strncpy(ar1,ar2,n);;两个string对象可以通过加法运算符+/+=实现合并操作(类似基本变量的运算赋值),对应于C-风格字符串中的常规函数char*strcat(ar1,ar2);  char*strncat(ar1,ar2,n);。还有,成员函数int size(); int length();对应于C-风格字符串中的常规函数int strlen(char*);。=/+/+=运算符也可以用于单个char值(ch变量或字符常量)赋值给string对象。另外,string对象也可以使用索引运算符[]访问特定位置的字符。
  • string对象的输入,除了cin>>方法,常用的方法是getline(cin/fin/instr,str,ch);(不是成员函数,它是string类的一个友元函数),并且这里并没有限制长度的参数,因为string对象可以自动调整自己的长度,这与C-风格字符串char数组getline的用法不同。另外,getline读取并丢弃分隔符。string对象的输出,常用cout<<方法。
  • string实际上是模板具体化basic_string<char>的一个typedef。size_type是一个依赖于实现的整型,在头文件string中定义的。string类7个构造函数:string(const char*);(字符串常量初始化或赋值时使用的方式)、string(size_type,char);、string(const string&);(赋值构造函数)、string();、string(const char*,size_type);、template<class Eter> string(Iter begin,Iter end);、string(const string&,string size_type =0,size_type =npos);
  • iostream类比string类出现早,在iostream类中没有处理string对象的类方法,但是方法cin>>和cout<<能用于string对象,并不是因为在iostream类中有函数定义,而是因为string类中的友元运算符重载函数(operator>>/<<(cin/cout,str);)。另外,string类还对全部6个关系运算符进行了重载,每个运算符都以三种方式被重载,以便能够将string对象与另一个string对象、C-风格字符串(数组名或字符串常量)进行比较,并能够将C-风格字符串(数组名或字符串常量)与string对象进行比较。通过运算符重载,有字符串常量参与时,可以将它视为未命名的string对象。
  • 其它,在结构中使用string对象做数据成员是可以的,但只添加string头文件还不行,一定要让结构定义能够访问名称空间std,即需使用using声明或using编译指令,或使用作用域解析运算符形式std::string str。如果需要多个字符串,可以声明一个string对象数组(字符串数组string str[];,str是一个指向string对象类型地址的指针,str[i]代表第i+1个string对象),而不是使用二维char数组。在字符串中搜索给定的子字符串或字符,可使用一系列成员函数find()。



字符串与函数

  • 字符串的比较:C-风格字符串比较int strcmp(char*,char*);(区分大小写),参数可以是char指针、字符串常量、数组名,因为这三个都是char地址类型(char指针,指向char类型地址的指针)。虽然不能用关系运算符比较字符串,但是可以让关系运算符比较字符,因为字符char是整型(可比较大小)。string类字符串比较可以使用!=等各种运算符,因为运算符重载,而且string对象和字符串常量(可视为未命名的string对象)作为一个表示字符串的实体。
  • C-风格字符串作为字符串的参数(char*ar)(const char*ar)(const char const*ar)(或使用char ar[]替换char*ar),传递的都是地址(数组名、字符串常量、char指针),可以使用const来禁止对char地址内的内容(字符串)的修改,第一个const固定内容第二个const固定指针。因为字符串内有NULL字符的停止标志,所以不必将字符串的长度作为参数传递给函数。while(*ar){... ar++;}或while(ar[i]){... i++;} 是一种处理字符串中字符的标准方式。
  • C-风格字符串做返回值char* function();,函数无法返回一个字符串数组,但可以返回一个字符串数组的首地址,这样比逐个返回字符会效率更高。函数使用new动态创建一个指向长度n的字符串的首地址的指针,然后返回该指针(首地址)。char*ar=new char[n+1];ar[n]='\0'; ... return ar; 是一种返回字符串首地址的标准方式。记得,new分配长度要为NULL字符多加一个长度(+1),并且在函数调用结束后使用delete[]释放new[]分配的内存(释放字符串使用的内存,并不释放当前的指针变量),而当函数调用结束,会释放指针变量ar的内存,不释放字符串使用的内存
  • string对象和函数,string function(string str)(const string str)(const string str[]); string对象做函数参数和返回值都是可以的,但只添加string头文件还不行,一定要让该函数声明和定义能访问名称空间std即需使用using声明或using编译指令,或使用作用域解析运算符形式std::string str。



字符串与指针

  • 指针名和数组名有点不同,指针在后期可以被修改、赋值、传递,不过字符串常量赋值仅限于初始化过程。尽管数组变量名从功能上来讲就是一个指针,但是数组名(数组首地址)在初始化过程完成赋值(或在数组变量声明时默认赋值),并在后期是不可修改的。字符串常量是个常量,所以它对char*的初始化会使用const关键字(const不是必须的,依赖编译器)。还有一点不同,就是使用sizeof()时,见上文。
  • 地址是C++中一种独立的类型,是指向某个基本类型(抽象类型)的一种复合类型,(数组名、字符串常量、char指针 等等)都是地址事实上,字符串常使用指针(而不是数组)来处理字符串。注意,未初始化的指针太随机有危险,尤其是输入操作,所以在建立指针时尽量尽快确定它指的地址。建议使用new动态分配内存的方法初始化指针,虽随机但没有危险。在讲字符串读入程序时,应使用已分配的内存地址,该地址可以是数组名,也可以是使用new初始化过的指针。
  • 如果要求cout<<输出char*指针变量的地址的话,需要类型转换(int*)ar或(void*)ar,其它类型指针不需要类型转换。






示例1:指针和字符串

#include<iostream>#include<cstring>int main(){using namespace std;char animal[20] = "bear";// 字符串常量初始化,数组名(指针)声明后或初始化后不可以改const char * bird = "wren";// 字符串常量初始化,const不是必须的,另外,bird指针可以修改char * ps;// 指针未初始化,危险cout << animal << " and ";cout << bird << "\n";//cout << ps << "\n";// 错误cout << "Enter a kind of animal: ";cin >> animal;// char数组可以重新写入,char*指针引导的字符串不可以重新写入字符//cin >> ps;// 危险ps = animal;// 不能用字符串常量,字符串常量只限制于初始化操作。另外,这里只是复制地址,并没有复制字符串bird = animal;// 修改指针birdcout << ps << "!\n";cout << bird << "!\n";cout << "Before using strcpy():\n";cout << animal << " at " << (int *) animal << endl;// 其它指针不需要类型转换(int*)cout << ps << " at " << (int *) ps << endl;ps = new char[strlen(animal) + 1];// 动态分配新的内存,没有危险strcpy(ps, animal);// 结合strlen()和new的动态分配,创建一个字符串副本cout << "After using strcpy():\n";cout << animal << " at " << (int *) animal << endl;cout << ps << " at " << (int *) ps << endl;delete [] ps;return 0;}




示例2:字符串在函数中的输入与返回

#include<iostream>#include<string>char * buildstr(char ch, int n);std::string strback(const std::string str[], int n);// 一定要让该函数声明和定义能访问名称空间std,才能使用string对象int main(){using namespace std;int times;char ch;string str1[2];cout << "Enter a character: ";cin >> ch;cout << "Enter an integer: ";cin >> times;char * ps = buildstr(ch, times);cout << ps << endl;delete [] ps;ps = buildstr('+', 20);cout << ps << "-DONE-" << ps << endl;delete [] ps;// 释放字符串使用的内存,不释放变量使用的内存cout << "Enter a string1: ";cin >> str1[0];cout << "Enter a string2: ";cin >> str1[1];string str2 = strback(str1, 2);cout << str2 << endl;return 0;}char * buildstr(char ch, int n){char * p_ar = new char[n + 1];// +1是为了存储NULL字符p_ar[n] = '\0'; while (n-- > 0)// 从后向前填充字符是为了避免使用额外的变量p_ar[n] = ch;return p_ar;// 函数结束后,释放变量p_ar的内存,不释放字符串使用的内存}std::string strback(const std::string str[], int n){std::string strb;while (n--)strb += str[n];strb += "  back-OK !";return strb;}




示例3:非图形版本的Hangman拼字游戏

#include<iostream>#include<string>#include<cstdlib>// for exit();#include<ctime>// for time();#include<cctype>// for tolower();using std::string;const int NUM = 26;const string wordlist[NUM] = {"apiary", "beetle", "cereal", "danger", "ensign", "florid", "garage", "health", "insult", "jackal", "keeper", "loaner", "mamage", "nonce", "oneset", "plaid", "quilt", "remote", "stolid", "train", "useful", "valid", "whence", "xenon", "yearn", "zippy"};// string对象数组int main(){using std::cin;using std::cout;using std::endl;using std::tolower;// 转换为小写字母的函数的名std::srand(std::time(0));// ???????????????char play;cout << "Will you play a word game? <y/n> ";cin >> play;play = tolower(play);// 转换成小写字母while (play == 'y'){string target = wordlist[std::rand() % NUM];// 随机产生,第3个构造函数int length = target.size();string attempt(length, '*');// 第2个构造函数string badchars;// 第4个构造函数int guesses = 6;cout << "Guess my secret word. Tt has " << length  << " letters, and you guess one letter at a time. You get " << guesses  << " wrong guesses." << endl;cout << "Your word: " << attempt << endl;while (guesses > 0 && attempt != target){char letter;cout << "Guess a letter: ";cin >> letter;if (badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)// find()查询字符或子字符串{cout << "You already guessed that, Try again." << endl;continue;}int loc = target.find(letter);// 若返回npos,说明没找到if (loc == string::npos){cout << "Oh, bad guess!" << endl;--guesses;badchars += letter;// string对象与char字符}else{cout << "Good guess!" << endl;attempt[loc] = letter;// string对象与char字符// check if letter appears againloc = target.find(letter, loc + 1);while (loc != string::npos){attempt[loc] = letter;// string对象使用[]loc = target.find(letter, loc + 1);}}cout << "You word: " << attempt << endl;if (attempt != target)// 两个string对象的比较{if (badchars.length() > 0)// 与size()一样cout << "Bad choies: " << badchars << endl;cout << guesses << " bad choies left." << endl;}}if (guesses > 0)cout << "That's right!" << endl;elsecout << "Sorry, the word is " << target << "." << endl;cout << "Will you play again? <y/n> ";cin >> play;play = tolower(play);}cout << "BYE!" << endl;return 0;}






本文总结自《C++ primer plus》(第六版中文版)第三、四、五、七、十六章

0 0
原创粉丝点击