第四章字符串和格式化输入or输出


title: 第四章 字符串和格式化输入/输出
author: HardyDragon
tags: C Notes


第四章 字符串和格式化输入/输出

4.1 前导程序

4.2 字符串简介

4.2.1 char类型数组和null字符

4.2.2 使用字符串

4.2.3 strlen()函数

4.3 常量和C预处理器

4.3.1 const限定符

4.3.2 明示常量

4.4 printf()和scanf()

4.4.1 printf() 函数

4.4.2 使用printf()

4.4.3 printf() 的转换说明修饰符

4.4.4 转换说明修饰符

4.4.5 使用scanf()

4.4.6 printf()和scanf()的*修饰符

4.4.7 printf()的用法提示

4.5 关键概念

4.6 本章小结

4.7 复习题

4.8 编程练习


4.1 前导程序

/* talkback.c -- 演示和用户交互*/
#include <stdio.h>
#include <string.h>
#define DENSITY 62.4
int main(int argc, char const *argv[])
{
    /* code */
    float weight, volume;
    int size, letters;
    char name[40];

    printf("HI,what's your first name?\n");
    scanf("%s", name);
    printf("%s,what's your weight in pounds?\n", name);
    scanf("%f", &weight);
    size = sizeof name;
    letters = strlen(name);
    volume = weight / DENSITY;
    printf("Well, %s ,your volume is %2.2f cubic feet.\n", name, volume);
    printf("Also,your first name has %d letters,\n", letters);
    printf("and we have %d bytes to store it.\n", size);
    getchar();
    getchar();
    return 0;
}

result

HI,what's your first name?
Chen
Chen,what's your weight in pounds?
130
Well, Chen ,your volume is 2.08 cubic feet.
Also,your first name has 4 letters,
and we have 40 bytes to store it.
  • 使用数组存储字符串,用户输入的名被存储在字符数组中,这个数组占用内存中连续的40个字节,每个字节存储一个字符值。
  • 使用%s转换说明来处理字符串的输入和输出。注意,在scanf()中,name没有&前缀,而weight有(&weight和name都是地址)
  • strlen()获取字符串长度

4.2 字符串简介

字符串是一个或多个字符的序列,双引号不是字符串的一部分,双引号只是告诉编译器它括起来的是字符串。

  • char类型数组和null字符

    C语言没有专门存储字符串的变量类型,字符串都被存储在char类型的数组中。数组由连续的存储单元组成,字符串中的字符被存储在相邻的存储单元中,每个单元存储一个字符。

    image-20201019085537825

    数组末尾的字符 ‘\0’ ,这是空字符,C语言用它来标记字符串的结束。空字符不是数字0,它是非打印字符,但是它的ASCII码为0。C中的字符串一定要以空字符结束,意味着数组的容量如要正常使用就需要比待存储的字符数多1,所以 程序中的 name[40] 数组只能存储39个字符,剩下一个字节留给空字符。

    再次明确一下数组的概念,数组可以看做一行连续的多个存储单元,数组是同类型数据元素的有序序列。以下声明创建一个包含40个存储单元的数组,每个单元存储一个char类型的值。

    char name[40];

    name后面的方括号【】表明这是一个数组,方括号中的40表明该数组中的元素数量。char 表示每个元素的类型。记得在字符串的末尾加上一个 ‘\0’ 。

  • 使用字符串

    // praise1.c -- 使用不同类型的字符串
    #include <stdio.h>
    #define PRAISE "You are an extraodinary being"
    int main(int argc, char const *argv[])
    {
          
          
        char name[40];
        printf("What's your name?");
        scanf("%s", name);
        printf("Hello,%s.%s\n", name, PRAISE);
        getchar();
        getchar();
    
        return 0;
    }
    
    

    result:

    What's your name?Hardy
    Hello,Hardy.You are an extraodinary being
    ------------------------------------------
    What's your name?Hardy Dragon
    Hello,Hardy.You are an extraodinary being
    

    %s告诉printf() 打印一个字符串。一个字符串存储在name数组中,另一个有 PRAISE 表示。

    我们可以不用把空字符放入字符串末尾,因为scanf() 在读取输入时就已经完成了这项工作。同样也不用在字符串常量 PRAISE 末尾添加空字符,编译器会自动帮忙在末尾加上空字符。

    注意:scanf()函数只是读取了 Hardy Dragon 中的Hardy,它在遇到第一个空白(空格、制表符或换行符)时就不再读取键盘的输入了。根据%s的转换说明,scanf() 只会读取字符串的一个单词,而不是一整句。C语言当然还有其他的输入函数用于处理各种输入情况例如 fgets() 用于读取一般字符串。

    字符串和字符:

    字符串常量"x" 和 ‘字符常量 x’ 的不同在于字符常量 ‘x’ 是基本类型,而字符串常量"x" 是派生类型;其实字符串常量"x" 是由两个字符常量组成的,‘x’ 和 ‘\0’

  • strlen() 函数

    之前用到了 sizeof 运算符,它以字节为单位给出对象大小。strlen() 函数给出字符串中的字符长度。因为1字节存储一个字符,所以sizeof() 和strlen() 对于字符串的效果是一样的么?

    // praise2.c 
    #include<stdio.h>
    #include<string.h>
    #define PRAISE "You are an extraodinary being"
    int main(int argc, char const *argv[])
    {
          
          
        char name[40];
    
        printf("What's your name?");
        scanf("%s", name);
        printf("Hello,%s.%s\n", name, PRAISE);
        printf("Your name of %zd letters occupies %zd memory cells.\n",
        strlen(name),sizeof name);
        printf("The phrase of price has %zd letters",strlen(PRAISE));
        printf("and occupies %zd memory cells.\n",sizeof PRAISE);
        
    
        getchar();
        getchar();
    
        return 0;
    }
    

    result:

    What's your name?Serendipity Chance
    Hello,Serendipity.You are an extraodinary being
    Your name of 11 letters occupies 40 memory cells.
    The phrase of price has 29 lettersand occupies 30 memory cells.
    

    sizeof 运算符报告 name数组只有40个存储单元。但是只有前11单元存储Serendipity ,所以strlen() 得出的结果是11,name数组的第十二个单元存储字符 即代表字符串结束的空字符 并未将其计入。

    **%zd 是C99和C11为sizeof 运算符的转换说明,对于strlen() 同样适用。**早期的C sizeof() 和strlen() 返回的实际类型是 unsigned 或 unsigned long。

    sizeof 何时使用圆括号,对于类型必须带圆括号,即sizeof(float);

    变量可以不带,但是尽量所有情况都是用圆括号。

    4.3 常量和C预处理器

    对于处理常量,建议使用C预处理器,例如

    #define TAXRATE 0.015

    编译程序的时候所有的 TAXRATE 都会被替换为0.015,这一过程被称为编译时替换。这样定义的常量也称为明示常量。其中的常量名为大写,这是为了方便在程序中浏览,提高可读性。

    还有一个不常用的命名约定,在名称前带c_ 或k_ 表示常量。

    // pizza.c -- 使用已经定义的常量
    #include <stdio.h>
    #define PI 3.14159
    int main(int argc, char const *argv[])
    {
        float area, circum, radius;
        
        printf("What is the radius of your pizza?\n");
        scanf("%f",&radius);
        area = PI * radius * radius;
        circum = 2.0 * PI *radius;
        printf("Your basic pizza parameters are as follows:\n");
        printf("circumference = %1.2f,area = %1.2f\n",circum,area);
        
        return 0;
    }
    
    

    result:

    What is the radius of your pizza?
    6.0
    Your basic pizza parameters are as follows:
    circumference = 37.70,area = 113.10
    

    其中printf() 语句中的 %1.2f 表明,结果被四舍五入为两位小数输出,省略写成%.2f 也可以。

    #define 指令也可以定义字符和字符串常量,注意单双引号的不同

    #define BEEP ‘\a’

    #define OOPS “Noew you have done it !”

    const 限定符

    C90 新加了 const 关键字,用于限定一个变量为只读,例如:

    const int MONTHS = 12; // MONTHS在程序中不可更改,值为12

    明示常量

    C头文件 limits.h 和 float.h 分别提供了与整数类型和浮点数类型大小限制相关的详细信息。例如 limits.h 头文件下包含类似代码,不同系统的不同:

    #define INT_MAX +32767

    #define INT_MIN -32768

    printf("\nINT_MAX = %d \n",INT_MAX);
    

    result

    INT_MAX = 2147483647
    

    还可以查看其它的明示常量,以此查看不同类型的取值范围,前提是引入头文件 limits.h

    image-20201019114849603

    float.h 头文件 也有相关的明示常量用以表示 float 类型相关的取值或信息。

    image-20201019115150301

    使用示例,需要编译器支持:

    // defines.c -- 使用 limits.h 和 float.h 头文件中定义的明示常量
    #include<stdio.h>
    #include<limits.h>
    #include<float.h>
    // 编译器要完全支持C99才能识别 LLONG_MIN 
    int main(int argc, char const *argv[])
    {
        printf("INT_MAX = %d\n",INT_MAX);
        printf("LLONG_MIN = %d\n",LLONG_MIN);
        printf("CHAR_BIT = %d\n",CHAR_BIT);
        printf("DBL_MAX = %d\n",DBL_MAX);
        printf("FLT_DIG = %d\n",FLT_DIG);
        printf("FLT_MIN = %d\n",FLT_MIN);
        printf("FLT_EPSILON = %d\n",FLT_EPSILON);
        return 0;
    }
    
    

    result: 这和书上的结果不同,可能是编译器的问题。

    INT_MAX = 2147483647
    LLONG_MIN = 0
    CHAR_BIT = 8
    DBL_MAX = -1
    FLT_DIG = 6
    FLT_MIN = 0
    FLT_EPSILON = 0
    

    4.4 printf() 和scanf()

    两个函数都是利用 格式化字符串 和 参数列表。

    image-20201019120735728

    // printout.c -- 使用转换说明
    #include<stdio.h>
    #define PI 3.141593
    int main(int argc, char const *argv[])
    {
          
          
        int number = 7;
        float pies = 12.75;
        int cost = 7800;
        printf("The %d contestants ate %f berry pies.\n",number,pies);
        printf("The value of pi is %f\n",PI);
        printf("Farewell ! thou art too dear for my possesing ,\n");
        printf("%c %d \n",'$',2*cost);
    
        return 0;
    }
    
    

    result:

    The 7 contestants ate 12.750000 berry pies.
    The value of pi is 3.141593
    Farewell ! thou art too dear for my possesing ,
    $ 15600
    
  • printf() 的转换说明修饰符

    image-20201019140529192

    tips:类型可移植性

    sizeof 运算符以字节为单位返回类型或值的大小,这应该是某种形式的整数,但是标准只规定这个返回值是无符号整数,在不同的情况下,有可能其是 unsigned int 、unsigned long 或 unsigned long long。所以根据不同系统要使用不同的转换说明符如%u,%lu,%llu。为了更好地移植,C标准提供了一个 stddef.h 的头文件将 size_t 定义成系统使用 sizeof 返回的类型,size_t 称为底层类型,然后使用 转换说明符 %z表示。类似的底层类型还有 ptrdiff_t 和 t 修饰符 来表示系统使用的两个地址差值。

    • 标记

      image-20201019141748718

      使用修饰符和标记的示例:

      // width.c -- 字段宽度
      #include<stdio.h>
      #define PAGES 959
      int main(int argc, char const *argv[])
      {
              
              
          printf("*%d*\n",PAGES); 
          printf("*%2d*\n",PAGES);  // 宽度两字符
          printf("*%10d*\n",PAGES);   
          printf("*%-10d*\n",PAGES);  // 宽度10 字符,左对齐
      
          return 0;
      }
      
      

      result:

      *959*
      *959*
      *       959*
      *959       *
      

      默对齐方式为右对齐。

      // floats.c -- 一些浮点数修饰符的组合
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          const double RENT = 3852.99;    // const 变量
      
          printf("*%f*\n",RENT);    
          printf("*%e*\n",RENT);    
          printf("*%4.2f*\n",RENT);    
          printf("*%3.1f*\n",RENT);    
          printf("*%10.3f*\n",RENT);    
          printf("*%10.3E*\n",RENT);    
          printf("*%+4.2f*\n",RENT);    
          printf("*%010.2f*\n",RENT);    
           
          return 0;
      }
      
      

      result:

      *3852.990000*
      *3.852990e+003*
      *3852.99*
      *3853.0*
      *  3852.990*
      *3.853E+003*
      *+3852.99*
      *0003852.99*
      
      // flags.c -- 演示一些格式标记
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          printf("%x %X %#x\n",31,31,31);
          // % d** 空格填充 
          printf("**%d**% d**% d**\n",42,42,-42);
          printf("**%5d**%5.3d**%05d**%05.3d**\n",6,6,6,6);
      
          return 0;
      }
      
      

      result:

      1f 1F 0x1f
      **42** 42**-42**
      **    6**  006**00006**  006**
      

      第三行输出演示了如何在整数格式中使用精度(%5.3d)生成足够的前导0以满足最小位数3的要求,5是字符宽度,.3是有效位数,默认以0填充,所以和(%05.3d)的效果一样。

      0标记遇到精度一起出现会忽略0标记。

      // stringf.c -- 字符串格式
      #include<stdio.h>
      #define BLURB "Authentic imitation"
      int main(int argc, char const *argv[])
      {
              
              
          printf("[%2s]\n",BLURB);
          printf("[%24s]\n",BLURB);
          printf("[%24.5s]\n",BLURB);
          printf("[%-24.5s]\n",BLURB);
      
          return 0;
      }
      
      

      result:

      [Authentic imitation]
      [     Authentic imitation]
      [                   Authe]
      [Authe                   ]
      

      注意:第一个转换说明是%2s,但是字段被扩大为可容纳字符串的所有字符。精度限制了待打印字符的个数,.5告诉printf() 函数只打印5个字符,另外,-标记使得文本左对齐输出。

    • 注意转换说明和待打印值的类型要匹配,不然会结果错误。其细节在于计算机的存储方式。

    • printf() 函数的返回值

      // prntval.c -- printf() 的返回值
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          int bph2o = 212;
          int rv;
      
          rv = printf("%d F is water's boiling point.\n",bph2o);
          printf("The printf() function printed %d characters.\n",rv);
      
          return 0;
      }
      
      

      result:

      212 F is water's boiling point.
      The printf() function printed 32 characters.
      

      返回的是字符数组的大小,所有字符,包括空格和不可见的换行符\n

      给字符串断行的三种方法:

      // longstrg.c -- 打印较长的字符串
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          printf("Here's one way to print a");
          printf("long string.\n");
          printf("Here's another way to print a \
      long string.\n");
          printf("Here's the newest way to print a "
          "long string.\n");  /* ANSI C*/
      
          return 0;
      }
      
      

      result:

      Here's one way to print along string.
      Here's another way to print a long string.
      Here's the newest way to print a long string.
      

      注意第二种方式使用反斜杠 断行,使得光标移至下一行,且字符串中不会包含换行符。但是值得注意的是下一行代码要从最左边开始才能正常显示。

      第三种是用两个双引号的字符串连接起来的,注意要在字符串中包含所需要的空格。

    • 使用scanf()

      scanf() 把输入的字符串转换成整数、浮点数、字符或字符串。

      • 如果使用scanf() 读取基本变量类型的值,在变量名前加上一个 &
      • 如果用scanf() 把字符串读入字符数组,不要使用 &
      // input.c -- 何时适用 &
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          int age;
          float assets;
          char pet[30];
      
          printf("Enter your age,assets,and favorite pet.\n");
          scanf("%d %f",&age,&assets);
          scanf("%s",pet);
          printf("%d $%.2f %s\n",age,assets,pet);
      
          return 0;
      }
      
      

      result:

      Enter your age,assets,and favorite pet.
      38
      92360.88 dog
      38 $92360.88 dog
      

      scanf() 函数使用空白(换行符、制表符和空格) 把输入分出多个字段。在依次把转换说明和字段匹配时跳过空白。值得注意的是 %c 转换说明,scanf() 会读取每个字符,包括空白。

      image-20201019172620057

      修饰符

      image-20201019173012975

      image-20201019173023265

      scanf() 函数会跳过整数前面的空白。除了%c 其他转换说明都会自动跳过待输入值前面的所有空白。例如 scanf("%d%d",&n,&m); 和 scanf("%d %d",&n,&m); 行为相同。但是对于%c来说加空格就会不同,例如:

      scanf("%c",&ch);

      从输入的第一个字符开始读取,而

      scanf(" %c",ch);

      则从第一个非空白字符开始读取。

    • scanf() 的返回值

      返回成功读取的项数,如果没有任何读取项,且需要读取一个数字而用户却输入一个非数值字符串,scanf() 便返回 0。当scanf() 检测到文件结尾时,会返回EOF(宏定义 -1)

    • printf() 和 scanf() 的 * 修饰符

      printf() 使用 * 修饰符用于占位

      // varwid.c -- 使用变宽输出字段
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          unsigned width,precision;
          int number = 256;
          double weight = 242.5;
      
          printf("Enter a field width:\n");
          scanf("%d",&width);
          printf("The number is :%d\n",width,number);
          scanf("%d %d",&width,&precision);
          // Weight = %width.precisionf
          printf("Weight = %*.*f\n",width,precision,weight);
      
          return 0;
      }
      
      

      result:

      Enter a field width:
      6
      The number is :6
      8 3
      Weight =  242.500
      

      * 作为占位符,后面的参数要对应。

      scanf() 的 * 用法

      // skiptwo.c -- 跳过输入中的前两个整数
      #include<stdio.h>
      int main(int argc, char const *argv[])
      {
              
              
          int n;
      
          printf("Please enter three integers:\n");
          // 读取输入时 带*的转化说明会被跳过
          scanf("%*d %*d %d",&n);
          printf("The last integer was %d\n",n);
      
          return 0;
      }
      
      

      result:

      Please enter three integers:
      2013 2014 2020
      The last integer was 2020
      

      这个 * 在程序需要读取文件中特定内容是比较有用。简称为跳过功能。

4.5 关键概念

可以把字符串存储在字符数组中,字符串无论表示为字符常量还是存储在字符数组中,都已一个叫做空字符的隐藏字符结尾。

可以使用const 声明只读变量。使用#define 定义数值常量(明示常量)提高程序可读性。

scanf() 和 printf() 的第一个参数中的转换说明必须与后续参数中的值相匹配,不然结果会异常。

scanf() 函数的变量参数要是地址才行,基本数据类型的变量要加& ,字符数组不用加。

除了 %c 模式(读取下一个字符),scanf() 在读取输入时会跳过非空白字符前的所有空白字符,然后一直读取字符,再遇到空白字符或和不匹配的字符就停下。

4.6 本章小结

注意scanf() 读取时候的缓冲区当一些字符没被正确赋值给变量时,剩下的字符便会放入缓冲区,再次遇到scanf() 函数时就会将之前剩下的输入再次读取,看看能不能匹配,不能匹配就拉闸。

4.7 复习题

printf("%2.2e",1201.0);
// 1.20e+003 e计数法
// 有符号的数字(包含小数点)后面紧跟e或者E,最后一个是有符号数表示10的指数。

判断一下的内容使用的转换说明符:

  1. 8A,字符宽度为4 的十六进制整数=》 %4X
  2. 字段宽度为15的 unsigned long 类型整数=》 %15lu
  3. 0x8a,宽度为4的十六进制整数=》 %#4x
  4. 2.33E+02, 宽度12,左对齐=> %-12.2E(注意eE大小写)
  5. 一个字段宽度为8,显示前8个字符的字符串=》 %8.8s
  6. 一个在参数列表中给定字段宽度的八进制整数=》 %*o
  7. 形如 +3.13 字段宽度等于数字中字符数的浮点数=》%+0.2f (不指定字符宽度)

读取下列输入的scanf() 语句,并声明语句中用到的变量和数组。

  1. 22.32 8.34E-09

    float kgs,share;
    scanf("%f %f",&kgs,&share);
    
  2. linguini

    char pasts[20];
    scanf("%s",pasts);
    
  3. catch 22 (跳过catch)

    int value;
    scanf("%*s %d",&value);
    

C语言的空白是指 空格、制表符、换行符,这3种空白是scanf() 函数进行输入的符号。

注意修饰符不能单独使用例如 %z(错误);

printf("double type size is %zd bytes",sizeof(double));
---
double type size is 8 bytes

不要作死用宏定义替换括号等重要符号。

4.8 编程练习

的 unsigned long 类型整数=》 %15lu
3. 0x8a,宽度为4的十六进制整数=》 %#4x
4. 2.33E+02, 宽度12,左对齐=> %-12.2E(注意eE大小写)
5. 一个字段宽度为8,显示前8个字符的字符串=》 %8.8s
6. 一个在参数列表中给定字段宽度的八进制整数=》 %*o
7. 形如 +3.13 字段宽度等于数字中字符数的浮点数=》%+0.2f (不指定字符宽度)

读取下列输入的scanf() 语句,并声明语句中用到的变量和数组。

  1. 22.32 8.34E-09

    float kgs,share;
    scanf("%f %f",&kgs,&share);
    
  2. linguini

    char pasts[20];
    scanf("%s",pasts);
    
  3. catch 22 (跳过catch)

    int value;
    scanf("%*s %d",&value);
    

C语言的空白是指 空格、制表符、换行符,这3种空白是scanf() 函数进行输入的符号。

注意修饰符不能单独使用例如 %z(错误);

printf("double type size is %zd bytes",sizeof(double));
---
double type size is 8 bytes

不要作死用宏定义替换括号等重要符号。

4.8 编程练习

猜你喜欢

转载自blog.csdn.net/qq_44880154/article/details/115104649
今日推荐