getchar() 和 putchar()

getchar() 函数不带任何参数,它从输入队列中返回下一个字符

下面的语句读取下一个字符输入,并把该字符的值赋给变量 ch

ch =getchar();

相当于

scanf("%c", &ch);

putchar() 函数打印它的参数

下面的语句把之前赋给 ch 的值作为字符打印出来

putchar(ch);

相当于

printf("%c", ch);

getchar() 和 putchar() 不需要转换说明,因为它们只处理字符

表示字符和字符串 I/O

字符串是以空字符(\0)结尾的 char 类型数组

在程序中定义字符串

1.字符串字面量(字符串常量)

用双引号括起来的内容称为字符串字面量(string literal),也叫作字符串常量(string constant)

双引号中的字符和编译器自动加入末尾的 \0 字符,都作为字符串储存在内存中

从 ANSI C 标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C 会将其视为串联起来的字符串字面量

char strings[50] = "Hello, and" " how are" "you"
" today!"; 等价于 char strings[50] = "Hello, and how are you today!";

如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠(\)

字符串常量属于静态存储类别(static storage class),这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次

用双引号括起来的内容被视为指向该字符串储存位置的指针

2.字符串数组和初始化

定义字符串数组时,必须让编译器知道需要多少的空间

一种方法是用足够空间的数组储存字符串

char strs[20] = "Hello World";

等价于

char strs[20] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'};

注意最后的空字符。没有这个空字符,这就不是一个字符串,而是一个字符数组

在指定数组大小时,要确保数组的元素个数至少比字符串长度多 1(为了容纳空字符)

所有未被使用的元素都被自动初始化为 0(这里的 0 指的是 char 形式的空字符,不是数字字符 0)

让编译器计算数组的大小只能用在初始化数组时

如果创建一个稍后再填充的数组,就必须在声明时指定大小

字符数组名和其他数组名一样,是该数组首元素的地址

char car[10] = "Tata";

car == &car[0];
*car == 'T';
*(car+1) == car[1] == 'a';

还可以用指针表示法创建字符串

char * pt1 = "Hello World";
char pt1[] = "Hello World";

pt1 和 ar1 都是该字符串的地址

3.数组和指针

数组形式在计算机的内存中分配,每个元素对应一个字符,还加上一个末尾的就、空字符 \0

通常,字符串都作为可执行文件的一部分储存在数据段中

当把程序载入内存时,也载入了程序中的字符串

字符串储存在静态储存区(static memory)中,但是,程序在开始运行时才会为该数组分配内存,此时才将字符串拷贝到数组中。此时的字符串有两个副本,一个是在静态内存中的字符串字面量,另一个是储存在数组中的字符串

指针形式也使得编译器为字符串在静态储存区为空字符预留个元素空间

一旦开始执行程序,它会为指针变量留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变,可以使用递增运算符来改变

字符串字面量被视为 const 数据

如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为 const

初始化数组把静态储存区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针

#include <stdio.h>
#define MSG "Hello World" int main()
{
char ar[] = MSG;
const char *pt = MSG;
printf("address of \"Hello World\": %p \n", "Hello World");
printf(" address of ar: %p\n", ar);
printf(" address of pt: %p\n", pt);
printf(" address of MSG: %p\n", MSG);
printf("address of \"Hello World\": %p \n", "Hello World"); return 0;
}

运行结果

pt 和 MSG 的地址相同,而 ar 的地址不同

虽然字符串字面量 "Hello World" 在程序的两个 printf() 函数中出现了两次,但是编译器只使用了一个存储位置,而且与 MSG 的地址相同,别的编译器可能在不同的位置储存

编译器可以把多次使用的相同字面量储存在一处或多处

静态数据使用的内存与 ar 使用的动态内存不同。不仅值不同,特定编译器甚至使用不同的位数表示两种内存

4.数组和指针的区别

“指向字符串”的意思是指向字符串的首字符

char ar[] = "Hello World";
const char *pt = "Hello World";

两者主要的区别是:数组名 ar 是常量,指针名 pt 是变量

两者都可以使用数组表示法:

#include <stdio.h>

int main()
{
char ar[] = "Hello World";
const char *pt = "Hello World"; for (int i = 0; i < 6; i++)
putchar(ar[i]);
putchar('\n'); for (int i = 0; i < 6; i++)
putchar(pt[i]);
putchar('\n'); return 0;
}

运行结果

两者都能进行指针加法操作

#include <stdio.h>

int main()
{
char ar[] = "Hello World";
const char *pt = "Hello World"; for (int i = 0; i < 6; i++)
putchar(*(ar + i));
putchar('\n'); for (int i = 0; i < 6; i++)
putchar(*(pt + i));
putchar('\n'); return 0;
}

运行结果

只有指针表示法可以进行递增操作

#include <stdio.h>

int main()
{
const char *pt = "Hello World";
while (*(pt) != '\0') // 在字符串末尾处停止
putchar(*(pt++)); // 打印字符,指针指向下一个位置
putchar('\n'); return 0;
}

运行结果

想让 ar 和 pt 统一

pt = ar;  // pt 现在指向数组 ar

这使得 pt 指针指向 ar 数组的首元素

这不会导致 pt 指向的字符串消失,这样只是改变了储存在 pt 中的地址

除非已经保存了 "Hello World" 的地址,否则当 pt 指向别处时,就无法再访问该字符串

但是不能这么做:

ar = pt;

这类似于 x = 3; 和 3 = x; 的情况

可以改变 ar 数组中的元素信息

ar[1] = 'a';

或者

*(ar + 1) = 'a';

数组的元素是变量(除非数组被声明为 const),但是数组名不是变量

char * word = "frame";
word[1] = 'l';

这样的行为是未定义的,可能导致内存访问错误

因为,编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量

char * p1 = "Klingon";
p1[0] = 'F';
printf("Klingon");
printf(": Beware the %ss!\n", "Klingon");

编译器可以用相同的地址替换每个 "Klingon" 实例

如果编译器使用这种单次副本表示法,并允许 p1[0] 修改 'F',那将影响所有使用该字符串的代码

建议在把指针初始化为字符串字面量时使用 const 限定符:

const char p1 = "Klingon";

把非 const 数组初始化为字符串字面量却不会导致类似的问题,因为数组获得的是原始字符串的副本

如果打算修改字符串,就不要用指针指向字符串字面量

指针和字符串

字符串的绝大多数操作都是通过指针完成的

#include <stdio.h>

int main(void)
{
const char * mesg = "Don't be a fool!";
const char * copy;
copy = mesg;
printf("%s\n", copy);
printf("mesg = %s; &mesg = %p; value = %p\n", mesg, &mesg, mesg);
printf("copy = %s; &copy = %p; value = %p\n", copy, &copy, copy);
return 0;
}

运行结果

指针 mesg 和 copy 分别储存在地址为 0061FF2C 和 0061FF28

最后两个指针的值,mesg 和 copy 的值都是 00405064,说明它们都指向同一个位置,指针的值就是它储存的地址

程序并未拷贝字符串

语句 copy = mesg; 把 mesg 的值赋给 copy,即让 copy 也指向 mesg 指向的字符串

字符串输入

分配空间

如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串

分配空间有两种方法

第一种,在声明时显示指明数组的大小

char name[5];

现在 name 是一个已分配 5 字节的地址

还有一种方法是使用 C 库函数来分配内存

不幸的 gets() 函数

gets() 函数读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个 C 字符串

gets() 函数经常和 puts() 函数配对使用

puts() 用于显示字符串,并在末尾添加换行符

#include <stdio.h>
#define STLEN 81 int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
gets(words); // 典型用法
printf("Your string twice:\n");
printf("%s\n", words);
puts(words);
puts("Done."); return 0;
}

运行结果

gets() 函数只知道数组的开始处,并不知道数组中有多少个元素

如果输入的字符串过长,会导致缓冲区溢出(buffer overflow),即多余的字符超出了指定的目标空间

gets() 的替代品

fgets() 函数(和 fputs())

fgets() 函数通过第 2 个参数限制读入的字符数来解决溢出的问题

fgets() 和 gets() 的区别:

  • fgets() 函数的第 2 个参数指明了读入字符的最大数量。如果该参数的值是 n,那么 fgets() 将读入 n-1 个字符,或者读到遇到的第一个换行符为止
  • 如果 fgets() 读到一个换行符,会把它储存在字符串中,gets() 会丢弃换行符
  • fgets() 函数的第 3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以 stdin(标准输入)作为参数,该标识符定义在 stdio.h 中

因为 fgets() 函数把换行符放在字符串的末尾(假设输入行不溢出),通常要与 fputs() 函数(和 puts() 类似)配对使用,除非该函数不在字符串末尾添加换行符。fputs() 函数的第 2 个参数指明它要写入的文件

如果要显示在计算机显示器上,应使用 stdout(标准输出)作为该参数

#include <stdio.h>
#define SELEN 14 int main(void)
{
char words[SELEN]; puts("Enter a string, please.");
fgets(words, SELEN, stdin);
printf("Your string twice (puts(), then fputs()):\n");
puts(words);
fputs(words, stdout);
puts("Enter another string, please.");
fgets(words, SELEN, stdin);
printf("Your string twice (puts(), then fputs()):\n");
puts(words);
fputs(words, stdout);
puts("Done."); return 0;
}

运行结果

第 1 次输入 apple pie,比 fgets() 读入的整行输入短,因此 apple pie\n\0 被储存在数组中。当 puts() 显示该字符串时又在末尾添加了换行符,apple pie 后面有一行空行。因为fputs()不在字符串末尾添加换行符,所以并未打印出空行

第 2 行输入 strawberry shortcake,超过了指定的大小,所以 fgets() 只读入了 13 个字符,并把 strawberry sh\0 储存在数组中

fputs() 函数返回指向 char 的指针

如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同

如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer),该指针保证不会指向有效的数据

在代码中,可以用数字 0 来代替,不过在 C 语言中用宏 NULL 来代替更常见(如果在读入数据时出现某些错误,该函数也返回 NULL)

空字符和空指针

空字符(或'\0')是用于标记 C 字符串末尾的字符,其对应字符编码是 0。由于其他字符的编码不可能是 0,所以不可能是字符串的一部分

空指针(或 NULL)有一个值,该值不会与任何数据的有效地址对应。通常,函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行

空字符是整数类型,而空指针是指针类型

空字符和空指针都可以用数值 0 来表示,但是两者是不同类型的 0

空字符是一个字符,占 1 字节;而空指针是一个地址,通常占 4 字节

gets_s() 函数

gets_s() 只从标准输入中读取数据,所以不需要第 3 个参数

gets_s() 会丢弃读到的换行符

如果gets_s()读到最大字符数都没有读到换行符,会执行以下几步

首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。接着,调用依赖实现的“处理函数”(或选择其他函数),可能会中止或退出程序

如果输入行太长,使用 gets() 会擦写现有数据,存在安全隐患

gets_s() 函数很安全,但是,如果不希望程序中止或退出,就要知道如何编写特殊的“处理函数”

如果打算让程序继续运行,gets_s() 会丢弃该输入行的其余字符,无论你是否需要

C Primer Plus学习笔记(十)- 字符串和字符串函数的更多相关文章

  1. C Primer Plus学习笔记(三)- 字符串和格式化输入/输出

    从一个简单的例子开始 #include <stdio.h> int main() { char name[10]; printf("Input Your Name:\n" ...

  2. <c++primer plus>学习笔记1之第八章函数探幽

    1 c++内联函数 编译器将使用相应的函数代码替换函数调用,对于内联代码,函数无需跳到另一个位置执行代码再跳回来,所以内联函数运行速度比常规函数快. 但是代价是需要更多的内存. 使用场合: 执行函数代 ...

  3. python3.4学习笔记(十五) 字符串操作(string替换、删除、截取、复制、连接、比较、查找、包含、大小写转换、分割等)

    python3.4学习笔记(十五) 字符串操作(string替换.删除.截取.复制.连接.比较.查找.包含.大小写转换.分割等) python print 不换行(在后面加上,end=''),prin ...

  4. python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例

    python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例 新浪爱彩双色球开奖数据URL:http://zst.aicai.com/ssq/openInfo/ 最终输出结果格 ...

  5. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  6. Learning ROS for Robotics Programming Second Edition学习笔记(十) indigo Gazebo rviz slam navigation

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 moveit是书的最后一章,由于对机械臂完全不知,看不懂 ...

  7. python3.4学习笔记(十八) pycharm 安装使用、注册码、显示行号和字体大小等常用设置

    python3.4学习笔记(十八) pycharm 安装使用.注册码.显示行号和字体大小等常用设置Download JetBrains Python IDE :: PyCharmhttp://www. ...

  8. python3.4学习笔记(十九) 同一台机器同时安装 python2.7 和 python3.4的解决方法

    python3.4学习笔记(十九) 同一台机器同时安装 python2.7 和 python3.4的解决方法 同一台机器同时安装 python2.7 和 python3.4不会冲突.安装在不同目录,然 ...

  9. python3.4学习笔记(十六) windows下面安装easy_install和pip教程

    python3.4学习笔记(十六) windows下面安装easy_install和pip教程 easy_install和pip都是用来下载安装Python一个公共资源库PyPI的相关资源包的 首先安 ...

  10. python3.4学习笔记(十) 常用操作符,条件分支和循环实例

    python3.4学习笔记(十) 常用操作符,条件分支和循环实例 #Pyhon常用操作符 c = d = 10 d /= 8 #3.x真正的除法 print(d) #1.25 c //= 8 #用两个 ...

随机推荐

  1. 探究platform_driver中“多态”思想

    问题最初是下面的两段代码引出的: static struct platform_driver sonypi_driver = { .driver = { .name = "sonypi&qu ...

  2. 【hive】函数大全

    数学函数 Return Type Name (Signature) Description DOUBLE round(DOUBLE a) Returns the rounded BIGINT valu ...

  3. LeetCode OJ :Unique Binary Search Trees II(唯一二叉搜索树)

    题目如下所示:返回的结果是一个Node的Vector: Given n, generate all structurally unique BST's (binary search trees) th ...

  4. C#基础知识梳理系列

    1. 这个系列深入的从IL层面谈了C#各种基本知识的本质,十分值得学习: http://www.cnblogs.com/solan/category/398748.html

  5. 打造属于自己的安卓menu

    首先,我们来看看这张图吧 看下面的menu菜单,是原装的菜单,好丑陋哦,类似于小编这么爱美的人来说,纯粹就是天大的打击,接受不起.于是,小编就发奋图强,努力,努力,再努力,终于,将菜单改的漂亮了一点, ...

  6. 目标跟踪之相关滤波:CF及后续改进篇

    一. 何为相关滤波? Correlation Filter 最早应用于信号处理,用来描述两个信号之间的相关性,或者说相似性(有点像早期的概率密度),先来看定义: 对于两个数据 f 和 g,则两个信号的 ...

  7. HAWQ取代传统数仓实践(十一)——维度表技术之维度合并

    有一种合并维度的情况,就是本来属性相同的维度,因为某种原因被设计成重复的维度属性.例如,在销售订单示例中,随着数据仓库中维度的增加,我们会发现有些通用的数据存在于多个维度中.客户维度的客户地址相关信息 ...

  8. AS3舞台的大小,可视区域大小及SWF文件的原始尺寸大小

    AS3舞台的大小,可视区域大小及SWF文件的原始尺寸大小三者之间没有什么关系. 当前可视区域:stage.stageWidth,stage.stageHeight.SWF文件编译后的原始尺寸大小:lo ...

  9. Git commit 信息标准和丢弃必须要的commit

    /***************************************************************************** * Git commit 信息标准和丢弃必 ...

  10. LeetCode 366. Find Leaves of Binary Tree

    原题链接在这里:https://leetcode.com/problems/find-leaves-of-binary-tree/#/description 题目: Given a binary tr ...