Skip to content

Latest commit

 

History

History
678 lines (455 loc) · 20 KB

File metadata and controls

678 lines (455 loc) · 20 KB

二十八、C

如果你懂 C++,你就差不多懂 C 了。使用 C 的经验给了你另一个吹牛的机会——一个字符长,所以应该合适!–在你的简历上。c 是操作系统和嵌入式系统的流行语言,里面有很多库。

c 基本上是我们在上课前所学的内容,不包括

  • SDL/SSDL

  • cincout

  • &参数

  • bool(用int代替)

  • constexpr(用const代替)

没有类、异常、重载运算符、模板或命名空间。存在,但没有成员函数或公共/私有/受保护部分(都是公共的)。

还有一些较小的差异,包括以下几点:

  • 铸造长这样,(int) f,不是这样:int (f)

  • struct S {...};确实声明了一个名为Sstruct,但是声明该类型的变量需要一个额外的单词:

    struct S myStruct;

cplusplus.com 和 cppreference.com,不管名字如何,都是 C 和 C++ 的好资源。

编译 C

Visual Studio 中,你不能选择“C 文件”作为添加到你的项目中的东西,但是你可以选择“C++ 文件”并将你的命名为<something>.c。编译器会将其视为 C 文件。照常编译和运行。

UnixMinGW 中,你可以将你的程序命名为<something>.c并用gcc命令进行编译,这就像g++只适用于 C 文件。示例代码将这一点构建到了它的 Makefiles 中。

这是必须的“你好,世界!”

// Hello, world! -- again!  This time in C.1
//    -- from _C++20 for Lazy Programmers_

#include <stdio.h>

int main ()
{
    printf ("Hello, world!\n");

    return 0;
}

Example 28-1“Hello, world!” in C

一些需要注意的事项:

  • Includ e 文件,无论是否属于系统,都以.h结尾。那些从 C 继承而来的 C++ 将首字母c去掉了:stdlib.h而不是cstdlibmath.h不是cmath

  • 我们使用printf打印–下一节将详细介绍。

Extra

如果您希望在同一个项目中同时包含 C++ 和 C 文件,这是可行的,但是需要一些技巧。您需要将main放在一个 C++ 文件中,并且对于要在 C++ 文件中使用的任何 C include 文件,这样包装 include:

extern "C"
{

#include "mycheader.h"
}

如果你用的是 gcc/g++,那就用 g++ 链接。

关于这方面的更多内容,在写作的时候,参见 C++ 超级 FAQ, isocpp.org/wiki/faq/mixing-c-and-cpp

输入-输出

所有这些 I/O 功能都在stdio.h中。

printf

代替cout >>,C 有函数printf(print-f,意为“带格式打印”):

printf ("Ints like %d, strings like %s, and floats like %f -- oh, my!\n",
         12, "ROFL", 3.14159);                   // %d is for "decimal"

将打印

Ints like 12, strings like ROFL, and floats like 3.141590 -- oh, my!

%序列是“格式字符串”("Ints like %d...")中的占位符,显示每个连续参数的位置。你可以有尽可能多的论点。最常见的%序列有%d表示十进制整数、%f表示固定浮点、%s表示字符串,即字符数组。%%的意思是“仅仅是%字符。”

你可以把修饰语放在里面。例如,%.2f将小数点右边的两位数字。

scanf和地址-of ( &)运算符

scanf(“扫描-f”)取代了cin >>,如下所示:

scanf ("%f %s", &myDouble, myCharArray);

&的意思是“记下…的地址”C 和 C++ 都有这个操作符,但是 C 一直在用。scanf需要知道myDouble在哪里,这样它就可以修改它(下一节将详细介绍)。它不需要myCharArray的地址,因为myCharArrayT5 是地址。

如果你使用本章中的scanf或其他一些函数,Visual Studio 会给出一个警告,告诉你这个函数是不安全的,就像使用一些cstring函数一样(见第 14 章)。如果您想禁用警告,请将此行放在main之前:

#pragma warning (disable:4996)

示例 28-2 演示printfscanf

// Program to test C's major standard I/O functions
//        -- from _C++20 for Lazy Programmers_

#include <stdio.h>

int main ()
{
    float number;           // number we'll read in and print out
    int   age;              // your age
    enum {MAXSTR = 80};2    // array size
    char  name [MAXSTR];    // your name

        // A printf showing float, and use of % sign
    printf ("%3.2f%% of statistics are made up on the spot!\n\n",
            98.23567894);

        // printfs using decimal, hex, and char array
        // %02d means pad number to a width of 2 with leading 0's
    printf ("%d is 0x%x in hexadecimal.\n\n", 16, 16);
    printf ("\"%s\" is a $%d.%02d word.\n\n", "hexadecimal",
            5, 0);

        // scanf needs & for the variables it sets
    printf ("Enter a floating-point number:  ");
    scanf ("%f", &number);
    printf ("%g is %f in fixed notation and %e in scientific.\n",
            number, number, number);
    printf ("...in scientific with a precision

of 2:  %.2e.\n\n",
            number);

        // ...except arrays, since they're already addresses
    printf ("Enter your name and age:  ");
    scanf ("%s %d", name, &age);
    printf ("%s is %d years old!\n\n", name, age);

    return 0;
}

Example 28-2printf and scanf

输出可能是这样的:

98.24% of statistics are made up on the spot!

16 is 0x10 in hexadecimal.

"hexadecimal" is a $5.00 word.

Enter a floating-point number: 2
2 is 2.000000 in fixed notation and 2.000000e+000 in scientific.
Here it is in scientific with a precision of 2: 2.00e+000.

Enter your name and age: Linus 7
Linus is 7 years old!

28-1 包含 printf 和 scanf 格式代码的部分列表。有关更多详细信息,请参见(在编写本报告时)cplusplus . com/reference/CST dio/printf/cplusplus . com/reference/CST dio/scanf/

fprintffscanffopenfclose

C 中的文件 I/O 使用了printfscanf的变体。考虑以下代码:

FILE* file = fopen ("newfile.txt", "w"); // open file
if (!file) { printf("Can't open newfile.txt!\n"); return 0; }
                                         // did it work? if not, quit main

fprintf (file, "Avagadro's number is %.4e.\n", 6.023e+023);
                                        // use it

fclose  (file);                         // close it

为了打开文件进行写入,我们调用fopen(“f-open”),给它一个文件名,"w"表示“写入”(到一个输出文件)。文件信息存储在一个类型为FILE*的指针中,如果fopen失败的话,这个指针就是NULL,相当于 C++ 的nullptr

关闭文件只是将文件指针发送到fclose,如图所示。

在这两者之间,通过添加file作为第一个参数,将printf修改为fprintf

如果你想读而不是写,打开文件,用"r"表示“读”,并类似地修改scanf:

file = fopen ("newfile.txt", "r");
if (!file) { printf("Can't open newfile.txt!\n"); return 0; }
fscanf (file, "%s %s %s %e", word1, word2, word3, &number);
fclose (file);

如果成功,fscanfscanf返回您给出的参数个数。如果号码不一样,那就是出了问题。可能您已到达文件末尾。诸如此类测试:

while (1)                            // while true
{
    if (fscanf (file, "%d", number) != 1)
        /* it didn't work -- handle that */;
    //...
}

示例 28-3 演示了这些功能。

// Program to test C's major standard I/O functions
//        -- from _C++20 for Lazy Programmers_

#include <stdio.h>

int main ()
{
    FILE* file;             // a file to write to or read from
    float number;           // number we'll read in and print out
    enum { MAXSTR = 80 };   // array size
    char junk [MAXSTR];     // a char array for reading in (and thus
                            //         discarding) a word

        // printing to file. The number gets 4 digits of precision
    file = fopen ("newfile.txt", "w");
    if (!file)
    {
        printf ("Can't open newfile.txt for writing!\n"); return 0;
    }
    printf  (      "Avagadro's number is %.4e.\n", 6.023e+023);
    fprintf (file, "Avagadro's number is %.4e.\n", 6.023e+023);
    fclose  (file);

        // reading from a file
    file = fopen ("newfile.txt", "r");
    if (!file)
    {
        printf("Can't open newfile.txt for reading!\n"); return 0;
    }
    fscanf (file, "%s %s %s %e", junk, junk, junk, &number);
                       // Read in 3 words, then the number we want
    fclose (file);
    printf ("Looks like Avagadro's number is still %.4e.\n", number);

    return 0;
}

Example 28-3fprintf, fscanf, fopen, and fclose

该文件将包含Avagadro's number is 6.0230e+023.,屏幕输出将为:

Avagadro's number is 6.0230e+023.
Looks like Avagadro's number is still 6.0230e+023.

sprintfsscanffgetsfputsputs

这里还有几个 I/O 函数。

  • sscanf**(“s-scan-f”)**从字符数组中读取。如果myCharArray"2.3 kg",我们可以说

    sscanf (myCharArray, "%f %s", &myDouble, myWord);
                // myDouble gets 2.3, myWord gets "kg"
    

    ,在 C++ 中我们会说

    sscanf (myCharArray, "%f %s", &myDouble, myWord);
                // myDouble gets 2.3, myWord gets "kg"
    
  • sprintf打印到一个字符数组:

    sprintf (myCharArray, "%s %f", name, number);
    

    在 C++ 中我们会说

    sstream myStringStream;
    myStringStream << name << number;
    string myString = myStringStream.str();
    
  • fgets从文件中读取一行文本:

    fgets (myCharArray, MAX_STRING, someFile); // read myCharArray, //   which should
                                               //   be no more than //   MAX_STRING long,
                                               //   from someFile
    

或者键盘:

  • fputs打印一个char数组到一个文件:
fgets (myCharArray, MAX_STRING, stdin);

fputs (myCharArray, someFile);

您可以将该文件命名为stdout,但通常我们会使用一个更短的版本:

puts (myCharArray); //sends to stdout

我们不会将fgets缩写为gets : gets存在,但被认为是不安全的,行为不像fgets,因此不被使用。

示例 28-4 演示了这些功能。

表 28-1

printfscanf的格式代码

|

%序列

|

意义

| | --- | --- | | %d | 十进制格式的整数。 | | %o | 无符号八进制整数(基数为 8)。 | | %x/%X | 无符号十六进制整数(基数为 16)。十六进制 1f 将显示为0x1f/0X1F。 | | %c | 性格。 | | %s | 字符数组。 | | %f | 定点浮点。 | | %e/%E | 科学记数法浮点。如果你说%E,那么E将是大写的。 | | %g/%G | 默认浮点。如果你说%G,那么E(如果有的话)将会是大写的。 | | %p | 指针。 | | %% | %人物本身。 |

// Program to test sprintf, sscanf, fgets, fputs, puts
//        -- from _C++20 for Lazy Programmers_

#include <stdio.h>

int main ()
{
    while (1)                  // forever, or until break...
    {
        enum {MAXLINE=256};    // array size for line
        char line [MAXLINE];   // a line of text
        enum {MAXSTR = 80};    // array size for word
        char word [MAXSTR];    // your word
        int  number;           // a number to read in

        // get an entire line with gets; on end of file quit
        printf("Enter a line with 1 word & 1 number, end of file to quit: ");
        if (! fgets (line, MAXLINE, stdin)) break;

        // repeat line with fputs
        printf("You entered:  ");
        fputs (line, stdout); // You *can* use fputs with stdout; puts is //   more usual

        // Use char array as source for 2 arguments
        if (sscanf (line, "%s %i", word, &number) != 2)
            puts ("That wasn't a word and a number!\n");
        else
        {
            // Print using sprintf and puts
            sprintf(line, "The name was %s and the number was %i.\n",
                    word, number);
            puts   (line);
            // If this weren't a demo of new functions, I'd say
            //    printf ("The name was %s and the number was %f.\n",
            //          name, number);
        }
    }

    puts ("\n\nBye!\n");

    return 0;
}

Example 28-4A program using sprintf, sscanf, fgets, and so on

样本输出:

Enter a line with 1 word and 1 numbers, end of file to quit: Mila 18
You entered:  Mila 18
The name was Mila and the number was 18.

Enter a line with 1 word and 1 numbers, end of file to quit: Catch 22
You entered:  Catch 22
The name was Catch and the number was 22.

Enter a line with 1 word and 1 numbers, end of file to quit: [Enter Ctrl-D or Ctrl-Z here]

Bye!

命令摘要

在表 28-2 中描述的函数中,如果我没有给出函数返回的含义,那是因为我们很少关心那个函数。有了fopenfgetsscanf家族,我们做到了。

表 28-2

C 语言中常见的stdio函数

|

printf和变体

|   | | --- | --- | | int``printfT2】...); | 根据formatString的规定,在formatString之后打印屏幕参数。 | | int``fprintfT2】const char* formatString, ...); | 与printf相同,但打印到file。 | | int``sprintfT2】const char* formatString, ...); | 与printf相同,但打印到str。 | | scanf和变体 |   | | int``scanfT2】...); | 按照formatString的指定,读取formatString之后的参数。如果在读取任何文件之前到达EOF,则返回EOF(文件结束);成功读取的 else #个参数。 | | int``fscanfT2】const char* formatString, ...); | 与scanf相同,但从file读取。 | | int``sscanfT2】const char* formatString, ...); | 与scanf相同,但从str读取 | | 打开/关闭文件 |   | | FILE*``fopenT2】const char* fileMode); | 打开由filename指定的文件并返回指针。常见的fileMode"r"(读)"w"(写)"a"(追加)。 | | int``fcloseT2】 | 关闭文件。 | | 读/写字符串 |   | | int``putsT2】int``fputsT2】FILE* file); | 打印str/打印strfile。 | | char*``fgetsT2】FILE* file); | 从file(可能是stdin)读取str,最多读取max-1个字符(所以str的大小应该是max或者更大)。失败时返回NULL。 |

防错法

  • scanf 因缺少 &:而失败

    scanf ("%f %s", myDouble, myCharArray);

    应该是

    scanf ("%f %s",``&T2】

    很容易忘记&,编译器可能不会警告你。

*传递参数

C 没有&参数,但和 C++ 一样,它认为一次函数调用不会改变其他参数。哦哦。

void swap (int arg1, int arg2)
{
    int temp = arg1; arg1 = arg2; arg2 = temp;
}

int main ()
{
    int x, y;
    ...
    swap (x, y);  // x, y will not be changed
    ...
}

c 希望您发送参数的地址:

int main ()
{
    int x, y;
    ...
    swap (&

x, &y); // x's and y's addresses are sent, not x and y
    ...
}

该函数获取该地址,并使用*来引用它所指向的东西,其中可以被改变:

void swap (int* arg1, int* arg2)
{
    int temp = *arg1; *arg1 = *arg2; *arg2 = temp;
}

有效!但是它很笨重,并且引入了一个令人恼火的常见错误:忘记*的。

示例 28-5 展示了一个使用它的程序(并且不要忘记*)。

// Program to do statistics on some strings
//        from _C++20 for Lazy Programmers_

#include <stdio.h>  // for printf, scanf
#include <string.h> // for strlen

void updateLineStats (char line[], unsigned int* length,
                      float* averageLineLength);

int main ()
{
    printf ("Type in a line and I'll reply. ");
    printf ("Type the end-of-file character to end.\n");

    while (1)   // forever (or until a break) ...
    {
        enum { MAXSTRING = 256 };      // max line length
        char line [MAXSTRING];         // the line
        int  length;                   // its current length
        float averageLineLength;

        // get line of input
        if (!fgets (line, MAXSTRING, stdin)) break;

        // do the stats. We send addresses, not variables, using &
        updateLineStats (line, &length, &averageLineLength);

        // give the result
        printf ("Length of that line, ");
        printf ("and average so far: %d, %.2f.\n",
                length, averageLineLength);
    }

    return 0;
}

void updateLineStats (char line[], unsigned int* length,
                      float* averageLineLength)
{
    static int totalLinesLength = 0;    // have to remember these
    static int linesSoFar = 0;          //   for next time

    // length is a pointer, so *length is the length
    *length = (unsigned int) strlen (line);
                    // casting from size_t to unsigned int
    // fgets included the final \n, but I won't count that:
    --(*length);

    ++linesSoFar;
    totalLinesLength += *length;

    *averageLineLength = // and averageLineLength needs its *, too
        totalLinesLength / ((float) linesSoFar);
}

Example 28-5Program using parameter passing with *’s in C

示例会话:

Type in a line and I'll reply. Type the end-of-file character to end.
alpha
Length of that line, and average so far: 5, 5.00.
bet
Length of that line, and average so far: 3, 4.00.
soup
Length of that line, and average so far: 4, 4.00.

防错法

  • **" <变量>的间接级别不同"或"无法从<类型>转换为<类型> * "或"在不强制转换的情况下从整数生成指针。"**有各种各样的抱怨方式,但底线是很难记住在函数调用中放入&的,更难记住*的 *every!时间到了!*你使用传入的变量。我从未找到解决办法。至少你知道这可能是出错的原因。

动态存储器

忘记newnew []deletedelete []。c 的动态内存更简单,虽然更难看:

#include <stdlib.h>                // for malloc, free

...

someType* myArray = malloc (myArraySize * sizeof (someType));
              // allocate a myArraySize element array of some type

...use the array...

free (myArray);                    // throw it back

没有析构函数来帮助你记住释放东西——你只能靠自己了。

示例 28-6 将示例 14-3(一个使用动态数组的早期程序)改编为 c。

// Program to generate a random passcode of digits
//        -- from _C++20 for Lazy Programmers_

#include <stdio.h>
#include <stdlib.h> // for srand, rand, malloc, free
#include <time.h>   // for time

int main ()
{
    srand ((unsigned int) time(NULL));// start random # generator
                                      // NULL, not nullptr

    int codeLength;                   // get code length
    printf ("I'll make your secret passcode. "
            "How long should it be? ");
    scanf ("%d", &codeLength);

                                        // allocate array
    int* passcode = malloc (codeLength * sizeof(int));

    for (int i = 0; i < codeLength; ++i)// generate passcode
        passcode[i] = rand () % 10;     // each entry is a digit

    printf ("Here it is:\n");           // print passcode
    for (int i = 0; i < codeLength; ++i)
        printf ("%d", passcode[i]);
    printf ("\n");
    printf ("But I guess it's not secret any more!\n");

    free (passcode);                   // deallocate array

    return 0;
}

Example 28-6A C program using dynamic memory

Exercises

做第 13 章和第 14 章的练习,不包括那些用 SSDL 的。

Footnotes [1](#Fn1_source)

如果你的编译器不理解像这样的注释//——“c++ 风格的注释”——改为注释/* C-style */。或者获得更新的编译器。

  2

enum hack”是声明MAXSTR的一种方式。它比const int MAXSTR=80需要更少的输入(以及内存和运行时间);。