C Primer Plus——2. 字符串 & 文件I/O

Outline:
· 字符串特性
· 字符串声明
· 初始化方法
· I/O
· 字符串函数
————————
1.字符串常量
是用引号包裹的,通常作为printf() puts()的参数,也可以用define来定义
如果字符串常量之间没有间隔或间隔为空格,则ANSI C会把它们串联起来加上一个空格。
字符串常量中使用双引号需要转义字符
字符串常量属于静态存储类(static storage),如果函数中用到字符串常量,不管调用这个函数多少次,这个字符串常量在程序运行期间只存储一份

  1. 字符串数组及初始化
    可以申请足够大的空间来放字符串
const char m1[40] = "hannahs"; //会自动加结束符,如果用一个一个字符来初始化,要记得自己加空字
// 符'\0',否则只是字符数组而不是字符串

规定数组大小时至少要比字符数大一,多出来的位置编译器会初始化为空字符
也可以让编译器决定数组大小。
用指针符号和用数组符号建立字符串都可以,指针符号允许自增自减而数组名不可以,字符串都是在静态存储区,在程序运行时为数组分配空间并把字符复制到数组中,而指针的话在程序运行时复制了字符串的地址。初始化分别如下

char heart[] = "I love China.";
char * head = "I love Suzhou.";

两者都可以用数组符号[]索引来取值,都可以用指针加法来取值(*(heart+i), *(head+i)),但只有指针可以自增

heart = head; // illegal
head = heart; //legal指针指向数组是可以的

指针用数组符号和索引进行修改时,会有Bus error内存访问错误
字符串数组

char * mystrs[5] = {"dafew","2dsfw","3sdf","4ds","5cs"}; // 数组存5个指针,指针指向字符串
//的第一个字符。双引号用来初始化一个指针

可以用mystrs[0][0]表示第一个字符串的首字母
于是,可以用二维数组来创建字符串数组

char mystrs2[5][81];

这样内存大小就固定了,指针数组的话长度是不固定的由初始化字符串决定长度。一个放5个完整的字符串,另一个放5个指针

  1. 字符串输入
    常用printf(), gets(), fgets()
    (1) gets()
    以换行符为分割,函数读取换行符后把结束符代替换行符。下一次的读取就从新的一行开始. gets()返回值是char指针,它的函数原型在stdio.h中
    gets()的构造如下:
char * gets( char * s){
  ...
  return s;
}// 如果读取出错会返回NULL
// so the error detection could be
while(gets(name)!=NULL){...}

(2) fgets()
gets()不检查存储区能不能放下实际输入的数据,多出来的溢出到相邻内存区,所以不安全。fgets()有参数指定大小,还有参数指定从哪个文件输入。

fgets(name, MAX, stdin); // MAX指定最多可读入MAX-1个字符,stdin指定从键盘读数据

fgets()读取直到换行符,会存下换行符,which is different from gets(),所以需要额外的动作来定位且删除换行符
(3) scanf()
从第一个非空白字符开始读,读到(但不包括)下一个空白字符.如果指定了字段宽度如%10s,则读10个字符或先遇到了空白字符。scanf的返回值为成功读取的项目数,或者遇到文件结束时返回EOF

char name1[11], name2[11];
printf("Enter 2 names\n");
scanf("%5d %10d", name1, name2);
printf("There are %s and %s", name1, name2);
输入>>>Portensia Callowit
输出>>>There are Porte and nsia
  1. 字符串输出
    puts(), fputs(), printf()
    (1) puts
#define DEF "I am a string."
int main(){
  char * str1 = "A pointer was initialized.";
  char str2[80] = "An array was initialized.";
  puts("arguments to put"); // puts()会自动在字符串结尾加换行符,双引号中的字符是常量,并被看作地址
  puts(DEF);
  puts(str1);
  puts(str1+4); // 从第5个符号开始打印到最后
  puts(str2);
  puts(&str2[4]);
}

puts()在遇到空字符的时候停下来,一般字符串初始化会有空字符,但是字符数组是没有空字符的!
(2) fputs()
需要第二个参数说明写到哪个文件,用stdout输出到屏幕。
fputs不加换行符!

char line[81];
while(gets(line)){
  puts(line);} //读取一行,在下一行回显
-------------------
char line[81];
while(fgets(line,81,stdin)){
  fputs(line,stdout);} // 回显在同一行

(3) printf()略

  1. 自定义字符串输入输出函数
    ++优先级比*高
while(*string != '\0')
while(*string) // 等价且beautiful
  1. 字符串函数
    string.h
    (1) strlen(a)
    不计'\0'
    (2) strcat(a, b)
    把b的副本添加到a的后面,达到字符串连接的效果,返回第一个字符串
    (3) strncat(a, b, n)
    strcat不检查第一个参数是否能容纳连接后的字符串。strncat规定最多可以添加n个字符
    (4) strcmp(a, b)
    如果直接拿str1==str2的话,不能进行字符串比较,因为这里比的是指针,除非指向同一个地方等号才会成立。所以字符串的比较通过strcmp(),如果相同就返回0, 返回正数说明a的ascii值大于b
    ascii 65 为A,97为a
    字符是可以直接比较的
    (5) strncmp(a, b, n)
    对前n个字符进行比较
    (6) strcpy & strncpy(target, src)
    字符串复制, 把src指向的字符串复制到target指向的数组中, target需要分配好空间,不能乱指。返回值是target(第一个参数)的值(一个地址)
// 判断字符串是否以s开头
if(temp[0] == 's')
if(strncmp(temp, "s", 1) == 0) // 等价

strcpy()和gets()一样,不检查目标是否能容纳源字符串。strncpy()用第三个参数来规定最大可复制的字符数
(7) sprintf(target, "...%...", list)
跟printf差不多,多了第一个参数目的字符串地址,为了输出到该地址instead of 屏幕
(8) strchr(target, c)
在目标字符串中寻找第一个字符c的位置,找到则返回该地址,否则返回空指针
(9)strrchr(target, c)
返回一个指针,指向目标串中最后一次出现c的地方
(10) strpbrk(const * char s1, const * char s2)
返回一个指针,指向了s2中任意一个字符which最早出现在s1中的位置
(11) strstr(s1,s2)
返回一个指针,指向s2第一次出现在s1中的位置(在1中找2)

// use fgets and remove \n
    printf("Enter your name:\n");
    char name[MAX];
    fgets(name, MAX-1, stdin);
    char * find;
    find = strchr(name,'\n');
    *find = '\0';
    puts(name);
// 输入多个字符串,判断输入结束
    char input[MAX][40];
    int cnt_in=0;
    char tmp[40];
    while (cnt_in
  1. 字符串转数字
    int atoi(str);
    atoi可以转换字符串形式的数据,甚至可以转换"42dsa"这样的,取开头数字部分。如果没有可以转换的数字,可能返回0
    atof()转为double
    atol()转为long
    ANSI C提供更复杂的函数,strtol(3个参数)转为long, strtoul(3个参数)转为unsigned long, strtod(2个参数)转为double,它们复杂在可以识别并报告字符串中非数字部分的第一个字符
long strtol(char * ptr, char ** endptr, int base);
char * end;
long num = strtol(argv[1], &end, 10); // 以10进制看待字符串数字,第二个参数是将指向结束指针的地址

当然,逆向有itoa, ftoa

====================================================================

第一部分中用重定向让程序和文件进行通信,但它完全和文件通信会失去与用户交互的机会。所以需要文件通信方法让我们可以在程序中打开文件然后用专门的I/O函数来读取写入文件

C将文件看成连续的字节序列,文件的两种视图:文本视图、二进制视图
MS-DOS:/r/n
Macintosh: /r
C : /n

fopen()
有两个参数,第一个是文件名(包含文件名字符串的地址),第二个是打开模式

model string meaning
"r" open and read file
"w" open and write, 先将文件长度截为0,如果不存在就创建
"a" open and write, 在文件尾追加内容,如果不存在就创建
"r+" open 可以更新也可以写入

//各种搭配+号,我也不懂。。带b的表示以二进制模式打开
如果用w打开文件,会清空文件原来的内容
fopen函数返回FILE指针,并不指向实际的文件,而是指向关于文件信息的数据包,可以知道缓冲区位置和缓冲区相关信息
磁盘不够、文件名非法、存取权限不够或硬件问题都可能导致fopen函数执行失败

getc() & putc()
与getchar() putchar()相似,需要告诉getc() putc()所使用的文件

ch = getc(fp); // 从fp指向的文件中得到一个字符
putc(ch, fp); // 写入
putc(ch, stdout); 
putchar(ch); // equal
char ch;
FILE * fp;
fp = fopen("words","r");
while((ch = getc(fp))!=NULL){
  putchar(ch);
}

fclose(fp)
关闭成功返回0,否则返回EOF

标准文件指针,都是FILE指针类型
stdin
stdout
stderr

文件IO函数
(1)fprintf()

char words[MAX];
fprintf(stderr,"Close file failed.\n");
while(fscanf(fp,"%s",words)==1){ // 会自动扫描文件从头到尾
  puts(words); // 回显
}

(2) fscanf()

while(fscanf(fp,"%s",words)==1){
/*code*/}

other: puts() fputs() gets() fgets()
以上都是顺序存取的IO函数

随机存取:fseek() & ftell()
fseek()有3个参数,第一个是FILE指针,第二个参数是offset且是Long类型的(数字加L)整数,第三个参数是起点模式
SEEK_SET:文件开始
SEEK_CUR:当前位置
SEEK_END:文件结尾
第二个参数为负时表示从起点向开头走N个字节
正常的话fseek返回0,如果试图移动超出文件范围,会返回-1

ftell返回long类型的文件当前位置。。/* 不会 */

long count, last;
fseek(fp, 0L, SEEK_END); // 定位到文件尾
last = ftell(fp); // 返回文件的字节长度
for(count; count<=last; count++){
  fseek(fp, -count, SEEK_END);
  ch = getc(fp); // fp会变???
}

在二进制模式下,C不支持SEEK_END模式
/* 懵 */

fgetpos() & fsetpos()
Long的范围大概在正负20亿,fseek ftell处理大文件还是有问题的
这俩函数用fpos_t这个新类型来代表位置
fgetpos()函数原型:

int fgetpos(FILE * restrict stream, fpos_t * restrict pos);

函数在pos位置上放置一个fpos_t值,成功返回0,否则返回非零

int fsetpos(FILE *steam, const fpos_t *pos);

int ungetc(int c, FILE * fp);

    char word[10];
    getchar(); // 消耗一个字符
    ungetc('a',stdin); // 把a放回输入流, 尝试文件指针不得行
    scanf("%s",word);
    puts(word); // 得到a开头的单词

int fflush(FILE * fp);
把缓冲区未写的数据发到fp,如果fp是空指针,就刷新输出缓冲

setvbuf()

二进制IO
int fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * fp);

// 保存一个256字节大小的数据对象
char buffer[256];
fwrite(buffer, 256, 1, fp);
// 保存10个double值的数组
 double earnings[10];
fwrite(earnings, sizeof(double), 10, fp);

//fread
fread(earnings, sizeof (double), 10, fp);// 把文件中的值复制到数组中

函数原型中ptr 是void类型因为数组类型不一定,void是可以cast到其他类型的

if (feof(fp)){
//文件读取结束(feof返回非0值)后执行的代码
}
if(ferror(fp)){
//文件读取错误(ferror返回非0值)后执行的代码
}

fread fwrite可以保留精度
getc fprintf保存文本信息

你可能感兴趣的:(C Primer Plus——2. 字符串 & 文件I/O)