用双括号括起来的内容称为字符串字面量,也叫做字符串常量。属于静态存储类别,如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命周期内存在,计时函数被调用多次。被双引号括起来的内容被视为指向该字符串储存位置的指针。
/*
输出结果:
We, 0000000000404000, t
根据%s打印出We,根据%p转换说明打印出一个地址,因此"are"代表一个地址
最后,*"the best"表示字符串所指向地址时储存的值,为该字符串的首字母 t
*/
printf("%s, %p, %c\n","We", "are", *"the best");
定义字符串数组时,必须让编译器知道需要多少空间。一种方法是用足够空间的数组储存字符串:
char m1[40] = "Limit yourself to one line's worth";
在指定数组大小时,要确保数组的元素至少比字符串的长度多1(为了容纳空字符),所有未被使用的元素都被自动初始化为0(这里的0指的是char形式的空字符,不是数字字符0)。
还可以让编译器确定数组的大小:
char m2[] = "If you can't think of anything,fake it.";
让编译器计算数组的大小只能用在初始化数组时,如果创建一个稍后再填充的数组,必须要在声明中指定大小:
//错误
char m3[];
m3 = "this is a test";
//正确
char m3[50];
m3[50] = "this is a test";
#include
#define MSG "I'm special"
int main(void){
char arr[] = MSG;
const char * pt = MSG;
printf("address of \"I'm special\" : %p \n", "I'm special");
printf(" address of arr: %p \n", arr);
printf(" address of pt: %p \n", pt);
printf(" address of MSG: %p \n", MSG);
printf("address of \"I'm special\" : %p \n", "I'm special");
/*
结果:
address of "I'm special" : 0000000000404000
address of arr: 000000000024FE30
address of pt: 0000000000404000
address of MSG: 0000000000404000
address of "I'm special" : 0000000000404000
分析:
----pt和MSG的地址相同,而arr的地址不同,说明了此时字符串有两个副本,一个在静态内存中
一个在arr数组中
----编译器会把多次使用的相同字面量储存在一处或多处,当前编译器是都储存在一处。别的编译器可能在不同的位置上储存3个"I'm special"
*/
return 0;
}
char heart[] = "I love U";
const char * head = "I love her";
两者主要的区别是:数组名heart是常量,而指针名head是变量。
// 指针数组,字符串数组
#include
#define SLEN 40
#define LIM 5
int main(void){
const char *m[LIM] = {
"this is a test1",
"this is a test2",
"this is a test3",
"this is a test4",
"this is a test5"
};
char y[LIM][SLEN] = {
"aaaaaaaaaaaaaa",
"bbbbbbbbbbbbbb",
"cccccccccccccc",
"dddddddddddddd",
"eeeeeeeeeeeeee"
};
int i;
for(i = 0; i < LIM; i++)
printf("%-36s %-25s\n",m[i],y[i]);
printf("\n某个字符:%c %c\n",m[1][1],y[2][2]);
printf("\nsizeof m = %u, sizeof y = %u\n",sizeof m,sizeof y);
/*
结果:
this is a test1 aaaaaaaaaaaaaa
this is a test2 bbbbbbbbbbbbbb
this is a test3 cccccccccccccc
this is a test4 dddddddddddddd
this is a test5 eeeeeeeeeeeeee
sizeof m = 40, sizeof y = 200
*/
return 0;
}
分析:
数组m和y都代表五个字符串,使用一个下标时都分别代表一个字符串,如m[1]和y[1];
使用两个下标时表示一个字符如 m[1][1],y[2][2],而且二者的初始方式也相同
区别:
1.数组m是一个内含五个指针的数组,在系统中占40个字节;而数组y为y[40][5]每个数组内有40个char类型的值,共占用200字节。
2.m中的指针指向初始化时所用的字符串常量的位置,而这些字符串常量被储存在静态内存中,而y数组中储存的是字符串常量的副本,所以每个字符串都被储存了两个;
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,它比二维字符串数组的效率高,但是,指针数组也有缺点:指针所指向的字符串常量不能更改,而二维字符串数组中的内容可以更改,所以,如果要更改字符串或者为字符串输入预留空间,不要使用指向字符串常量的指针 。
要做的第一件事就是分配空间。
//错误示例
char *name;
scanf("%s",name);
//因为scanf()要把信息拷贝到参数指定的地址上,而此时参数是个未初始化的指针,name可能指向任何地方
#include
int main(void){
char words[2];
puts("Enter a string, please.");
gets(words);
printf("Your string twice:\n");
printf("%s\n", words);
puts(words);
puts("Done.");
/*
问题出现在gets()唯一的参数是words,它无法检测数组是否装得下输入行,gets()只知道数组开始处
并不知道数组有多少个元素,如果输入的字符串过长,会导致缓冲区溢出。
*/
return 0;
}
fgets()函数通过第二个参数限制读入的字符数来解决溢出的问题。该函数专门设计用于处理文件输入,所以一般情况下不太好用。fgets()和gets()的区别如下:
#include
#define STLEN 14
int main(void){
char words[STLEN];
puts("Enter a string, please.");
fgets(words,STLEN,stdin);
printf("**************fputs**************\n");
fputs(words,stdout);
puts("**************puts*******************");
puts(words);
puts("**************printf*******************");
printf("%s",words);
puts("Done!");
/*当输入"test"时,小于STLEN,会读取换行符
Enter a string, please.
test
**************fputs**************
test
**************puts*******************
test
**************printf*******************
test
Done!
当输入"this is a test"时,长度大于STLEN,不会读取换行符
Enter a string, please.
this is a test
**************fputs**************
this is a tes**************puts*******************
this is a tes
**************printf*******************
this is a tesDone!
*/
return 0;
}
#include
#define STLEN 10
int main(void){
char words[STLEN];
int i;
puts("Enter strings (empty line to quit):");
//如果按了回车,或者输入大于等于STLEN-1个字符就会进入循环
//也就是说此时字符末尾一定是'\n'或者'\0'
while (fgets(words,STLEN,stdin) != NULL && words[0] != '\n'){
i = 0;
//通过循环一直跳到字符末尾
while(words[i] != '\n' && words[i] != '\0')
i++;
//如果是换行符就用'\0'替换
if(words[i] == '\n')
words[i] = '\0';
else //如果 word[i] == '\0',说明字符长度已经到了STLEN-1个,它之后的字符要忽略
while (getchar() != '\n')
continue;
puts(words);
}
puts("Done!");
return 0;
}
printf("sizeof NULL = %u\n",sizeof NULL);
printf("sizeof '\\0' = %u\n",sizeof '\0');
结果:
sizeof NULL = 8
sizeof '\0' = 4
//用法
gets_s(word,STLEN);
与fgets()的区别:
scanf()更像是“获取单词”函数,而不是获取字符串函数。
#include
int main(void){
char name[11];
char name1[11],name2[11];
int count;
scanf("%s",name);
printf("Please enter 2 names .\n");
count = scanf("%5s %10s",name1,name2);
printf("I read the %d names %s 、 %s and %s.\n",count, name1, name2,name);
return 0;
}
C有3个标准库函数用于打印字符串:put()、fputs()和printf()。
//使用puts()
#include
#define DEF "I am a #define string."
int main (void){
char str1[80] = "An array was initialized to me";
const char * str2 = "A pointer was initialized to me.";
/*
puts()函数只需把字符串的地址作为参数传递给它即可,遇到空字符时就停止输出,所以要确保字符串中有空字符。
*/
puts("I am argument to puts().");
puts(DEF);
puts(str1);
puts(str2);
puts(&str1[5]);
puts(str2 + 4);
return 0;
}
//my_puts 用户自定义输出函数
#include
void put1(const char *);
int put2(const char *);
int main(void){
put1("If I had much money");
put1(" as I could spend,\n");
printf("I count %d characters.\n",
put2("I never would cry old chairs"));
return 0;
}
void put1(const char * string){//不会改变字符串
while(*string) //与 *string != '\0'相同
putchar(*string++); //先使用后自加
}
int put2(const char * string){
int count = 0;
while(*string){
putchar(*string++);
count++;
}
putchar('\n');
return count;
}
C库提供了多个处理字符串的函数,ANSI C把这些函数的原型放在string.h头文件中。
常用的有:strlen() strcat() strcmp() strncmp() strcpy() 和strncpy()。
用于统计字符串长度。
#include
#include
void fit(char *, unsigned int );
int main(void){
char msg [] = "Thing should be as simple as possible,"" but not simpler.";
printf("The length of msg: %d\n",strlen(msg));
printf("%s\n",msg);
puts(msg);
fit(msg,38);
puts(msg);
puts("more:");
puts(msg + 39);
printf("%s\n",msg);
return 0;
}
void fit(char *string, unsigned int size){
if(strlen(string) > size)
//puts()和printf()函数会在空字符处停止输出,并忽略其余字符
string[size] = '\0';
}
//结果:
The length of msg: 55
Thing should be as simple as possible, but not simpler.
Thing should be as simple as possible, but not simpler.
Thing should be as simple as possible,
more:
but not simpler.
Thing should be as simple as possible,
strcat()函数用于拼接字符串,接收两个字符串作为参数,该函数把第二个字符串的备份附加在第一个字符串的末尾,并把拼接后形成的新字符串作为第一个字符串,第二个字符串不变。strcat()函数的类型是char *(即指向char的指针),返回第一个参数的地址。
#include
#include
#define SIZE 80
char * s_gets(char * st, int n);
int main(void){
char flower[SIZE];
char addon [] = "s smell like old shoes.";
puts("What is your favorite flower");
char * ch = s_gets(flower, SIZE);
puts(ch);
printf("%p\n",ch);
printf("%p\n",flower);
if(ch){
strcat(flower,addon);
puts(flower);
puts(addon);
}
else{
puts("End of file encountered!");
}
puts("bye");
return 0;
}
char * s_gets(char * st, int n){
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);//如果一切进行顺利,返回的是第一个字符的地址 ,遇到文件结尾或者错误时返回NULL
if(ret_val){//如果输入成功
while (st[i] != '\n' && st[i] != '\0')//跳转到字符串末尾
i++;
if(st[i] == '\n')//替换掉换行符
st[i] = '\0';
else//如果输入字符串超出字数,之后的字符就省略
while (getchar() != '\n')
continue;
}
return ret_val;//输入正常则返回字符串首字符地址
}
strcat()函数无法检查第一个数组是否能容纳第二个字符串。如果分配给第一个数组的空间不够大,多出来的字符溢出到相邻的存储单元时就会出问题。strncat()函数的第三个参数指定了最大添加字符数。例如,
strncat(bugs, addon, 13);
//把addon字符串的内容附加给bugs,在加到第13个时或遇到空字符时停止。因此第一个数组应该足够大。
#include
#include
#define SIZE 5
#define BUGSIZE 13
char * s_gets(char * st, int n);
int main(void){
char flower[SIZE];
char addon [] = "s smell like old shoes.";
char bug[BUGSIZE];
int available;
puts("What is your favorite flower");
char * ch = s_gets(flower, SIZE);
if((strlen(addon) + strlen(flower) + 1) <= SIZE)
strcat(flower,addon);
puts(flower);
puts("What is your favorite bug?");
s_gets(bug,BUGSIZE);
available = BUGSIZE - strlen(bug) - 1;//算上了空字符串
strncat(bug, addon, available);
puts(bug);
return 0;
}
char * s_gets(char * st, int n){
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);//如果一切进行顺利,返回的是第一个字符的地址 ,遇到文件结尾或者错误时返回NULL
if(ret_val){//如果输入成功
while (st[i] != '\n' && st[i] != '\0')//跳转到字符串末尾
i++;
if(st[i] == '\n')//替换掉换行符
st[i] = '\0';
else//如果输入字符串超出字数,之后的字符就省略
while (getchar() != '\n')
continue;
}
return ret_val;//输入正常则返回字符串首字符地址
}
如果两个字符串参数相同,该函数就返回0,否则就返回非零值。
它比较的是字符串,而不是整个数组。
#include
#include
#define SIZE 40
#define ANSWER "Grant"
char * s_gets(char * st, int n);
int main(void){
char try[SIZE];
puts("Who is buried in Grant's tomb?");
s_gets(try,SIZE);
while (strcmp(try, ANSWER) != 0){
printf("%d\n",strcmp(try, ANSWER));
puts("No,that is wrong.Try again.");
s_gets(try,SIZE);
}
puts("That is right!");
return 0;
}
char * s_gets(char * st, int n){
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);//如果一切进行顺利,返回的是第一个字符的地址 ,遇到文件结尾或者错误时返回NULL
if(ret_val){//如果输入成功
while (st[i] != '\n' && st[i] != '\0')//跳转到字符串末尾
i++;
if(st[i] == '\n')//替换掉换行符
st[i] = '\0';
else//如果输入字符串超出字数,之后的字符就省略
while (getchar() != '\n')
continue;
}
return ret_val;//输入正常则返回字符串首字符地址
}
strcmp()函数比肩字符串的字符,直到发现不同的字符为之,这一过程可能会持续到字符串的末尾。而strncmp()函数在比较字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。
#include
#include
#define LISTSIZE 6
int main(void){
const char * list[LISTSIZE] = {
"astronomy","astounding",
"astrophysics","ostracize",
"asterism","astrophobia"
};
int count = 0;
int i;
for(i = 0; i < LISTSIZE; i++){
//第三个参数的数字是指定用多少个第二的字符串的字符与第一个字符串作比较,如果为2则用“as"与第一个字符串作比较
if(strncmp(list[i],"astro",6) == 0){
printf("Found:%s\n",list[i]);
count++;
}
}
printf("The list contained %d words beginning with astro.\n",count);
return 0;
}
strcpy()函数相当于字符串赋值运算符。
第一个参数是目标字符串,第二个参数是源字符串。
char target[20];
int x;
x = 4; //数字赋值
strcpy(target,"Hello!");//字符串赋值
target = "Clippers";//语法错误
程序员要确保目标数组有足够的空间容纳源字符串的副本,以下的代码有问题:
char * str:
strcpy(str,"Hi");//有问题
str未被初始化,所以该字符串可能被拷贝到任意地方。
-strcpy()的返回类型是char *,该函数返回的是第一个参数的值,即一个字符的地址。
第一个参数不必指向数组的开始,这个属性可用于拷贝数组的一部分。
#include
#include
#define SIZE 40
#define LIM 5
char * s_gets(char * st, int n);
int main(void){
char qwords[LIM][SIZE];
char temp[SIZE];
int i = 0;
const char * orig = "beast";
char copy[40] = "Be the best that you can be.";
char *ps;
puts(orig);
puts(copy);
ps = strcpy(copy + 7,orig);
puts(copy);
puts(ps);
puts("***********************");
printf("Enter %d words beginning with q:\n",LIM);
while (i < LIM && s_gets(temp, SIZE)){
if(temp[0] != 'q')
printf("%s doesn't begin with q !\n",temp);
else{
strcpy(qwords[i], temp);
i++;
}
}
puts("Here ate the words accepted:");
for(i = 0; i < LIM; i++){
puts(qwords[i]);
}
return 0;
}
char * s_gets(char * st, int n){
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);//如果一切进行顺利,返回的是第一个字符的地址 ,遇到文件结尾或者错误时返回NULL
if(ret_val){//如果输入成功
while (st[i] != '\n' && st[i] != '\0')//跳转到字符串末尾
i++;
if(st[i] == '\n')//替换掉换行符
st[i] = '\0';
else//如果输入字符串超出字数,之后的字符就省略
while (getchar() != '\n')
continue;
}
return ret_val;//输入正常则返回字符串首字符地址
}
strcpy()和strcat()函数都有同样的问题,他们都不能检查目标空间是否有足够的容量容纳源字符串的副本。
//读入字符串并排序
#include
#include
#define SIZE 81 //限制字符串长度,包括\0
#define LIM 20 //可读入的最多行数
#define HALT "" //空字符串停止输入
void ststr(char * st [], int num);//字符串排序函数
char * s_gets(char * st, int n);
int main(void){
char input[LIM][SIZE];
char *ptstr[LIM];//内含指针变量的数组,指针数组里面存储的是指针,每一个元素占8个字节
int ct = 0;
int k;
char * test[] = {"this","is","testing"};
printf("%p\n",test[0]);
printf("%p\n",test[1]);
printf("%u\n",sizeof test);
printf("Input up to %d lines, and I will sort them .\n",LIM);
printf("To stop,press the Enter key at a line's start.\n");
//输入行数小于LIM,输入没有错误,输入的首字符不是空字符,则循环输入
while (ct < LIM && s_gets(input[ct], SIZE) != NULL && input[ct][0] != '\0'){
ptstr[ct] = input[ct];//把每一行的地址都传入ptstr
ct++;
}
//输入完毕,进行排序
ststr(ptstr,ct);
for( k = 0; k < ct; k++)
puts(ptstr[k]);
return 0;
}
void ststr(char *strings [], int num){
char * temp;
int top,seek;
/*
选择排序算法:
第一个大循环:第一个元素分别和第二个,第三个。。。作比较,较大的元素放在第一位 做完一轮比较后, 第一个元素为最大的
第二个大循环:第二个元素分别与第三个第四个元素比较,较大的放在第二个元素上,比较结束后,第二大的元素位于排在第二
。。。
在这里是排序指针,而不是字符串
*/
for(top = 0; top < num-1; top++)
for(seek = top + 1; seek < num; seek++)
if(strcmp(strings[top], strings[seek]) > 0){
temp = strings[top];
strings[top] = strings[seek];
strings[seek] = temp;
}
}
char * s_gets(char * st, int n){
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);//如果一切进行顺利,返回的是第一个字符的地址 ,遇到文件结尾或者错误时返回NULL
if(ret_val){//如果输入成功
while (st[i] != '\n' && st[i] != '\0')//跳转到字符串末尾
i++;
if(st[i] == '\n')//替换掉换行符
st[i] = '\0';
else//如果输入字符串超出字数,之后的字符就省略
while (getchar() != '\n')
continue;
}
return ret_val;//输入正常则返回字符串首字符地址
}
在C语言中,我们通常会使用 scanf 和 printf 来对数据进行输入输出操作。在C++语言中,C语言的这一套输入输出库我们仍然能使用,但是 C++ 又增加了一套新的、更容易使用的输入输出库。
C++ 中的输入与输出可以看做是一连串的数据流,输入即可视为从文件或键盘中输入程序中的一串数据流,而输出则可以视为从程序中输出一连串的数据流到显示屏或文件中。
在编写 C++ 程序时,如果需要使用输入输出时,则需要包含头文件iostream,它包含了用于输入输出的对象,例如常见的cin表示标准输入、cout表示标准输出、cerr表示标准错误。
使用 cout 进行输出时需要紧跟<<运算符,使用 cin 进行输入时需要紧跟>>运算符,这两个运算符可以自行分析所处理的数据类型,因此无需像使用 scanf 和 printf 那样给出格式控制字符串。
“<<” 和 “>>” 提供了 C++ 语言的字符串输入和字符串输出功能。"<<" 可以将字符读入一个流中(例如 ostream);">>" 可以实现将以空格或回车为 “结束符” 的字符序列读入到对应的字符串中,并且开头和结尾的空白字符不包括进字符串中。
char name[20];
cin.getline(name,20);//读取20个字符到name
#include
#include
using namespace std;
void main ()
{
string s1, s2;
getline(cin, s1);
getline(cin, s2, ' ');
cout << "You inputed chars are: " << s1 << endl;
cout << "You inputed chars are: " << s2 << endl;
}
string类位于命名空间std中,所以需要使用using来应用,或者通过std::string来引用。,它隐藏了字符串的数组性质,可以像处理普通变量一样处理字符串。
#include
#include
using std::cout;
using std::cin;
using std::endl;
using std::string;
int main(void) {
string str1;
string str2 = "Hello";
cout << "Enter your name;" << endl;
cin>>str1;
//string类操作
str2 += " "+str1;
cout << str2 << endl;
//求字符串长度
cout << "The size of str2 is :"<<str2.size() << endl;
cout << str2[2] << endl;
//使用getline输入字符串
string str3;
getline(cin, str3);
cout << str3 << endl;
return 0;
}