<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ C语言 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ C语言 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 19:37:18 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/c-programming/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何在 C 语言中打开、关闭和写入文件 ]]>
                </title>
                <description>
                    <![CDATA[ 如果你以前写过 C 语言的 helloworld 程序，你已经知道 C 语言里的基本文件 I/O： /* C 语言里的简单的 hello world */ #include <stdlib.h> // 导入 IO 函数 #include <stdio.h> int main() {     // This printf is where all the file IO magic happens!     // How exciting!    ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/file-handling-in-c-how-to-open-close-and-write-to-files/</link>
                <guid isPermaLink="false">63b14e7b836d320782be11d4</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Fri, 23 Dec 2022 01:26:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/01/5f9c9d31740569d1a4ca3667.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/file-handling-in-c-how-to-open-close-and-write-to-files/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">File Handling in C — How to Open, Close, and Write to Files</a>
      </p><p>如果你以前写过 C 语言的 <code>helloworld</code> 程序，你已经知道 C 语言里的基本文件 I/O：</p><pre><code class="language-c">/* C 语言里的简单的 hello world */
#include &lt;stdlib.h&gt;

// 导入 IO 函数
#include &lt;stdio.h&gt;

int main() {
    // This printf is where all the file IO magic happens!
    // How exciting!
    printf("Hello, world!\n");
    return EXIT_SUCCESS;
}</code></pre><p>文件处理是编程中最重要的部分之一。在 C 语言中，我们使用一个文件类型的结构指针来声明一个文件：</p><pre><code class="language-c">FILE *fp;</code></pre><p>C 语言提供了一些内建函数来执行基本的文件操作：</p><ul><li><code>fopen()</code> - 创建一个新文件或打开一个现有文件</li><li><code>fclose()</code> - 关闭一个文件</li><li><code>getc()</code> - 从文件中读取一个字符</li><li><code>putc()</code> - 将一个字符写到一个文件中</li><li><code>fscanf()</code> - 从一个文件中读取一组数据</li><li><code>fprintf()</code> - 将一组数据写到一个文件中</li><li><code>getw()</code> - 从文件中读取一个整数</li><li><code>putw()</code> - 将一个整数写到一个文件中</li><li><code>fseek()</code> - 将位置设置为期望的点</li><li><code>ftell()</code> - 给出文件中的当前位置</li><li><code>rewind()</code> - 将位置设置为起始点</li></ul><h3 id="-">打开一个文件</h3><p><code>fopen()</code> 函数用于创建一个文件或打开一个现有文件：</p><pre><code class="language-c">fp = fopen(const char filename,const char mode);</code></pre><p>有许多模式可以打开一个文件：</p><ul><li><code>r</code> - 以读模式打开文件</li><li><code>w</code> - 以写模式打开或创建一个文本文件</li><li><code>a</code> - 以附加模式打开一个文件</li><li><code>r+</code> - 以读写模式打开一个文件</li><li><code>a+</code> - 以读写模式打开一个文件</li><li><code>w+</code> - 以读写模式打开一个文件</li></ul><p>下面是一个从文件中读取数据和向文件中写入数据的例子：</p><pre><code class="language-c">#include&lt;stdio.h&gt;
#include&lt;conio.h&gt;
main()
{
FILE *fp;
char ch;
fp = fopen("hello.txt", "w");
printf("Enter data");
while( (ch = getchar()) != EOF) {
  putc(ch,fp);
}
fclose(fp);
fp = fopen("hello.txt", "r");

while( (ch = getc(fp)! = EOF)
  printf("%c",ch);
  
fclose(fp);
}</code></pre><p>现在你可能在想，“这只是把文本打印到屏幕上。这个文件怎么会是 IO？”</p><p>答案起初并不明显，需要对 UNIX 系统有一些了解。在 UNIX 系统中，一切都被视为文件，这意味着你可以从文件中读取和写入文件。</p><p>这意味着你的打印机可以被抽象为一个文件，因为你对打印机所做的就是对它进行写入。把这些文件看作是流也是很有用的，因为你将在后面看到，你可以用 shell 重定向它们。</p><p>那么，这与 <code>helloworld</code> 和文件 IO 有什么关系？</p><p>当你调用 <code>printf</code> 时，你实际上只是在向一个叫作 <code>stdout</code> 的特殊文件写东西，这是 <strong><strong><strong><strong>standard output</strong></strong></strong></strong>（标准输出）的简称。<code>stdout</code> 代表由你的 shell 决定的标准输出，通常是终端。这就解释了为什么它会打印到你的屏幕上。</p><p>有两个流（即文件）可供你使用，即 <code>stdin</code> 和 <code>stderr</code>。<code>stdin</code> 代表 <strong><strong><strong><strong>standard output</strong></strong></strong></strong>（标准输出），你的 shell 通常将其连接到键盘上。<code>stderr</code> 代表 <strong><strong><strong><strong>standard error</strong></strong></strong> output</strong>（标准错误输出），你的 shell 通常将其连接到终端。</p><h3 id="-io-">基本文件 IO，或我如何学习创建管道</h3><p>理论够多了，让我们写点代码吧！写入文件的最简单方法是使用输出重定向工具 <code>&gt;</code> 来重定向输出流。</p><p>如果你想追加，你可以使用 <code>&gt;&gt;</code>：</p><pre><code class="language-bash"># This will output to the screen...
./helloworld
# ...but this will write to a file!
./helloworld &gt; hello.txt</code></pre><p>毫不奇怪，<code>hello.txt</code> 的内容将是：</p><pre><code class="language-text">Hello, world!</code></pre><p>假设我们有另一个叫作 <code>greet</code> 的程序，类似于 <code>helloworld</code>，它有一个给定的 <code>name</code>：</p><pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main() {
    // 初始化一个数组，存有 name
    char name[20];
    // 读取一个字符串，并把它存到 name
    scanf("%s", name);
    // 打印 greeting
    printf("Hello, %s!", name);
    return EXIT_SUCCESS;
}</code></pre><p>我们可以使用 <code>&lt;</code> 工具将 <code>stdin</code> 重定向为从文件中读取，而不是从键盘上读取：</p><pre><code class="language-bash"># 写一个包含 name 的文件
echo Kamala &gt; name.txt
# 这将从文件读取 name，并把 greeting 打印到屏幕上
./greet &lt; name.txt
# ==&gt; Hello, Kamala!
# 如果你想也把 greeting 写入一个文件，你可以使用 “&gt;”</code></pre><p>注意：这些重定向操作符是在 <code>bash</code> 和类似的 shell 中。</p><h3 id="--1">重要内容</h3><p>上面的方法只适用于最基本的情况。如果你想做更大、更好的事情，你可能想从 C 语言中而不是通过 shell 来处理文件。</p><p>为了达到这个目的，你将使用一个叫作 <code>fopen</code> 的函数。这个函数需要两个字符串参数，第一个是文件名，第二个是模式。</p><p>模式基本上是权限，所以 <code>r</code> 代表读，<code>w</code> 代表写，<code>a</code> 代表追加。你也可以把它们结合起来，所以 <code>rw</code> 意味着你可以读和写该文件。还有更多的模式，但这些是最常用的。</p><p>在你有了 <code>FILE</code> 指针之后，你可以使用基本上与你原来使用的相同的 IO 命令，只是你必须在它们前面加上 <code>f</code>，并且第一个参数将是文件指针。例如，<code>printf</code> 的文件版本是 <code>fprintf</code>。</p><p>下面是一个名为 <code>greetings</code> 的程序，它从一个包含名字列表的文件中读取问候语，并将问候语写到另一个文件中：</p><pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main() {
    // 创建文件指针
    FILE *names = fopen("names.txt", "r");
    FILE *greet = fopen("greet.txt", "w");

    // 检查是否一切没问题
    if (!names || !greet) {
        fprintf(stderr, "File opening failed!\n");
        return EXIT_FAILURE;
    }

    // Greetings time!
    char name[20];
    // 持续读取所有内容
    while (fscanf(names, "%s\n", name) &gt; 0) {
        fprintf(greet, "Hello, %s!\n", name);
    }

    // 到达末尾时，打印一条信息到终端以通知用户
    if (feof(names)) {
        printf("Greetings are done!\n");
    }

    return EXIT_SUCCESS;
}</code></pre><p><code>names.txt</code> 包含：</p><pre><code class="language-text">Kamala
Logan
Carol</code></pre><p>然后，在运行 <code>greetings</code> 之后，文件 <code>greet.txt</code> 将包含：</p><pre><code class="language-text">Hello, Kamala!
Hello, Logan!
Hello, Carol!</code></pre> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ C 语言中的三元运算符 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：Ternary Operator in C Explained [https://www.freecodecamp.org/news/c-ternary-operator/] 开发者们可以使用三元运算符实现多个条件的判断，以取代较长的 if-else 条件语句。 三元运算符有三个参数：  * 第一个是一个比较参数  * 第二个是正确比较后的结果  * 第三个是错误比较后的结果 把三元运算符看作是写 if-else 语句的一种速记方法，会有帮助。下面是一个使用 if 和 else 进行简单决策的例子。 int a = 10, b = 20, c; if (a < b) {     c = a; } else {  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/c-ternary-operator/</link>
                <guid isPermaLink="false">6213590823041306357608da</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Tue, 15 Mar 2022 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/03/5f9c9db2740569d1a4ca3922.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/c-ternary-operator/">Ternary Operator in C Explained</a></p><p>开发者们可以使用三元运算符实现多个条件的判断，以取代较长的 <strong>if-else</strong> 条件语句。</p><p>三元运算符有三个参数：</p><ul><li>第一个是一个比较参数</li><li>第二个是正确比较后的结果</li><li>第三个是错误比较后的结果</li></ul><p>把三元运算符看作是写 if-else 语句的一种速记方法，会有帮助。下面是一个使用 if 和 else 进行简单决策的例子。</p><pre><code class="language-c">int a = 10, b = 20, c;

if (a &lt; b) {
    c = a;
}
else {
    c = b;
}

printf("%d", c);</code></pre><p>这个例子需要十多行，但这并不是必须的。你可以使用三元运算符，只用三行代码就能写出上述程序。</p><h3 id="-"><strong><strong><strong><strong>语法</strong></strong></strong></strong></h3><p><code>condition ? value_if_true : value_if_false</code></p><p>如果条件 <code>condition</code> 符合，语句的结果是 <code>value_if_true</code>，反之，结果是 <code>value_if_false</code>。</p><p>下面是使用三元运算符改写上述例子。</p><pre><code class="language-c">int a = 10, b = 20, c;

c = (a &lt; b) ? a : b;

printf("%d", c);</code></pre><p>上述例子的输出结果是：</p><pre><code class="language-c">10</code></pre><p><code>c</code> 的值应该是 <code>a</code>，因为条件 <code>a &lt; b</code> 为真。</p><p>记住，参数 <code>value_if_true</code> 和 <code>value_if_false</code> 必须是相同的类型，而且它们必须是简单的表达式，而不是完整的语句。</p><p>三元运算符可以像 if-else 语句一样被嵌套。考虑一下下面的代码：</p><pre><code class="language-c">int a = 1, b = 2, ans;
if (a == 1) {
    if (b == 2) {
        ans = 3;
    } else {
        ans = 5;
    }
} else {
    ans = 0;
}
printf ("%d\n", ans);</code></pre><p>下面是使用嵌套三元运算符重写的上面的代码：</p><pre><code class="language-c">int a = 1, b = 2, ans;
ans = (a == 1 ? (b == 2 ? 3 : 5) : 0);
printf ("%d\n", ans);</code></pre><p>上面两个例子的结果都应该是：</p><pre><code class="language-c">3</code></pre> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ C 语言中的变量作用域——局部和全局作用域 ]]>
                </title>
                <description>
                    <![CDATA[ 在编程中，你经常需要处理变量的作用域。变量的作用域决定了你是否可以在特定代码块内访问和修改它。 在本教程中，你将了解 C 编程语言中的变量作用域。你将看到一些代码示例，以帮助你了解局部变量和全局变量之间的差异。 变量的作用域是什么 在继续了解局部和全局变量作用域之前，让我们了解作用域的含义。 > 简单来说，变量的作用域就是它在程序中的生命周期。 这意味着变量的作用域是整个程序中变量被声明、使用和可以修改的代码块。 在下一节中，你将了解变量的局部作用域。 C 语言中变量的局部作用域——嵌套块 在本节中，你将了解局部变量如何在 C 语言中工作。你将首先编写几个示例，然后概括作用域原则。 ▶ 这是第一个示例： #include <stdio.h> int main()  {     int my_num = 7;     {         //给 my_num 加上 10   ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/scope-of-variables-in-c-local-and-global-scope-explained/</link>
                <guid isPermaLink="false">6139db1fb201730648bd9771</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 变量 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Thu, 09 Sep 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/09/Scope-of-variables-in-c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在编程中，你经常需要处理变量的作用域。变量的作用域决定了你是否可以在特定代码块内访问和修改它。</p><p>在本教程中，你将了解 C 编程语言中的变量作用域。你将看到一些代码示例，以帮助你了解局部变量和全局变量之间的差异。</p><h2 id="-">变量的作用域是什么</h2><p>在继续了解局部和全局变量作用域之前，让我们了解作用域的含义。</p><blockquote>简单来说，变量的作用域就是它在程序中的生命周期。</blockquote><p>这意味着变量的作用域是整个程序中变量被声明、使用和可以修改的代码块。</p><p>在下一节中，你将了解变量的局部作用域。</p><h2 id="c-">C 语言中变量的局部作用域——嵌套块</h2><p>在本节中，你将了解局部变量如何在 C 语言中工作。你将首先编写几个示例，然后概括作用域原则。</p><p>▶ 这是第一个示例：</p><pre><code class="language-c">#include &lt;stdio.h&gt;

int main() 
{
    int my_num = 7;
    {
        //给 my_num 加上 10
        my_num = my_num +10;
        //或者使用更简洁的 my_num +=10 -
        printf("my_num is %d",my_num);
    }
    
    return 0;
}
</code></pre><p>让我们了解一下上面的程序是做什么的。</p><p>在 C 语言中，你用 <code>{}</code> 分隔代码块。左花括号和右花括号分别表示块的开始和结束。</p><ul><li><code>main()</code> 函数有一个整数变量 <code>my_num</code>，它在代码块外部被初始化为 7。</li><li>代码块内部有一行代码将 10 添加到变量 <code>my_num</code>。</li></ul><p>现在，编译并运行上面的程序，输出结果：</p><pre><code>//输出

my_num is 17</code></pre><p>你可以看到以下内容：</p><ul><li>代码块内部能够访问在代码块外部声明的 <code>my_num</code> 的值，并通过向其添加 7 来修改它。</li><li>如输出所示，<code>my_num</code> 的值现在为 17。</li></ul><h2 id="c-2">C 语言中变量的局部范围——嵌套块示例 2</h2><p>▶ 这是另一个相关的例子：</p><pre><code class="language-c">#include &lt;stdio.h&gt;

int main() 
{
    int my_num = 7;
    {
        int new_num = 10;
    } 
    printf("new_num is %d",new_num); //this is line 9
    return 0;
}</code></pre><ul><li>在这个程序中，<code>main()</code> 函数在代码块外部有一个整数变量 <code>my_num</code>。</li><li>另一个变量 <code>new_num</code> 在代码块内部初始化。</li><li>我们试图在代码块外部访问和打印代码块内部的 <code>new_num</code> 的值。</li></ul><p>如果你尝试编译上面的代码，你会注意到它没有成功编译。你将收到以下错误消息：</p><pre><code>Line   Message
9      error: 'new_num' undeclared (first use in this function)</code></pre><blockquote>这是因为变量 <code>new_num</code> 是在代码块内部声明的，其作用域仅限于代码块内部。换句话说，它对于代码块内部是本地的，不能从代码块外部访问。</blockquote><p>基于上述观察，让我们写下以下变量局部作用域的通用原则：</p><pre><code>{
	/*OUTER BLOCK*/
    
      {
    	
        
        //此块开始之前的代码块外部的内容
        //可以在这里被访问
        
        /*INNER BLOCK*/
        
        
      }
     
       //此处无法访问代码块内部的内容
 }</code></pre><h2 id="c--1">C 语言中变量的局部作用域——不同的块</h2><p>在前面的示例中，你了解了如何无法从代码块外部访问嵌套代码块内部的变量。</p><p>在本节中，你将了解在不同块中声明的变量的局部作用域。</p><pre><code class="language-c">#include &lt;stdio.h&gt;

int main()
{
    int my_num = 7;
    printf("%d",my_num);
    my_func();
    return 0;
}

void my_func()
{
    printf("%d",my_num);
}</code></pre><p>在上面的例子中，</p><ul><li>在 <code>main()</code> 函数中声明整型变量 <code>my_num</code> 。</li><li>在 <code>main()</code> 函数内部，打印出 <code>my_num</code> 的值。</li><li>还有另一个函数 <code>my_func()</code> 尝试访问和打印 <code>my_num</code> 的值。</li><li>由于程序从 <code>main()</code> 函数开始执行，因此在 <code>main()</code> 函数内部调用了 <code>my_func()</code>。</li></ul><p>▶ 现在编译并运行上述程序，你将收到以下错误消息：</p><pre><code>Line   Message
13     error: 'my_num' undeclared (first use in this function)</code></pre><p>如果你注意到，在第 13 行，函数 <code>my_func()</code> 尝试访问在 <code>main()</code> 函数中声明和初始化的 <code>my_num</code> 变量。</p><blockquote>因此，变量 <code>my_num</code> 的作用域被限制在 <code>main()</code> 函数中，并且被称为 <code>main()</code> 函数的局部变量。</blockquote><p>我们可以将这种局部作用域的概念一般性地表示如下：</p><pre><code>{

	/*BLOCK 1*/
    // 此处无法访问 BLOCK 2 的内容
    
}


{

	/*BLOCK 2*/
    // 此处无法访问 BLOCK 1 的内容
    
}
</code></pre><h2 id="c--2">C 语言中变量的全局作用域</h2><p>到目前为止，你已经了解了 C 语言中变量的局部作用域。在本节中，你将学习如何在 C 语言中声明全局变量。</p><p>▶ 让我们从一个例子开始。</p><pre><code class="language-c">#include &lt;stdio.h&gt;
int my_num = 7;

int main()
{
    printf("my_num can be accessed from main() and its value is %d\n",my_num);
    //call my_func
    my_func();
    return 0;
}

void my_func()
{
  printf("my_num can be accessed from my_func() as well and its value is %d\n",my_num);
}
</code></pre><p>在上面的例子中，</p><ul><li>在函数 <code>main()</code> 和 <code>my_func()</code> 之外声明变量 <code>my_num</code> 。</li><li>我们尝试访问 <code>main()</code> 函数中的 <code>my_num</code>，并打印其值。</li><li>我们在 <code>main()</code> 函数中调用 <code>my_func()</code> 函数。</li><li>函数 <code>my_func()</code> 也尝试访问 <code>my_num</code> 的值，并将其打印出来。</li></ul><p>该程序编译没有任何错误，输出如下所示：</p><pre><code>//输出
my_num can be accessed from main() and its value is 7
my_num can be accessed from my_func() as well and its value is 7</code></pre><p>在这个例子中，有两个函数——<code>main()</code> 和 <code>my_func()</code>。</p><p>但是，变量 <code>my_num</code> 不是程序中任何函数的局部变量。这种对任何函数都不是局部的变量被称为具有全局作用域，称为全局变量。</p><p>全局变量作用域的原理可以总结如下：</p><pre><code>//所有全局变量都在这里声明
function1()
	{
    
    // 所有全局变量都可以在 function1 中访问
    
    }
function2()
	{
    
    // 所有全局变量都可以在 function2 中访问
     
    }
    </code></pre><h2 id="--1"><strong>总结</strong></h2><p>在本教程中，你了解了局部作用域和全局作用域之间的区别。这是关于 C 语言中变量作用域的介绍性教程。</p><p>在 C 语言中，有一些访问修饰符来控制变量的访问级别。你可以在声明变量时使用相应的关键字来更改访问权限。</p><p>祝你编码愉快！</p><p>原文：<a href="https://www.freecodecamp.org/news/scope-of-variables-in-c-local-and-global-scope-explained/">Variable Scope in C – Local and Global Scope Explained</a>，作者：<a href="https://www.freecodecamp.org/news/author/bala-priya/">Bala Priya C</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 Vs Code 中编译 C++ ]]>
                </title>
                <description>
                    <![CDATA[ C ++ 是一种静态类型的、自由格式的、（通常）已编译的、多范例的、中级通用的编程语言。 简而言之，C ++ 是基于 C 的复杂、高效、通用的编程语言。 它是由 Bjarne Stroustrup [http://www.stroustrup.com/] 于 1979 年开发的。 C ++ 的主要功能之一是编译器。这用于编译和运行 C ++ 代码。 > 编译器是一种特殊程序，用于处理以特定编程语言（如 C ++）编写的语句，并将其转换为计算机处理器使用的机器语言或“代码”。 我之所以写这篇文章，是因为我有一个 C ++任务，需要使用编译器。像往常一样，每个人都在使用 CodeBlocks  IDE [http://www.codeblocks.org/] 和 Visual Studio IDE [https://visualstudio.microsoft.com/]。但是我已经习惯了 Visual Studio Code 来编写所有编程内容。 然后，我开始寻找在我自己的 Vs Code 编辑器中直接编译 C ++的方法，也就有了本文:)。 在本文中，我将向你展示如何在 V ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-compile-your-c-code-in-visual-studio-code/</link>
                <guid isPermaLink="false">5fe066c739641a0517d5229f</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ VSCode ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 编译器 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Mon, 22 Mar 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/12/banner-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>C ++ 是一种静态类型的、自由格式的、（通常）已编译的、多范例的、中级通用的编程语言。</p><p>简而言之，C ++ 是基于 C 的复杂、高效、通用的编程语言。</p><p>它是由 <a href="http://www.stroustrup.com/">Bjarne Stroustrup</a> 于 1979 年开发的。</p><p>C ++ 的主要功能之一是编译器。这用于编译和运行 C ++ 代码。</p><blockquote>编译器是一种特殊程序，用于处理以特定编程语言（如 C ++）编写的语句，并将其转换为计算机处理器使用的机器语言或“代码”。</blockquote><p>我之所以写这篇文章，是因为我有一个 C ++任务，需要使用编译器。像往常一样，每个人都在使用 <a href="http://www.codeblocks.org/">CodeBlocks &nbsp;IDE</a> 和 <a href="https://visualstudio.microsoft.com/">Visual Studio IDE</a>。但是我已经习惯了 Visual Studio Code 来编写所有编程内容。</p><p>然后，我开始寻找在我自己的 Vs Code 编辑器中直接编译 C ++的方法，也就有了本文:)。</p><p>在本文中，我将向你展示如何在 Vs Code 中设置编译器，并为你提供一些优秀 C ++ 资源的链接。</p><h1 id="-"><strong>准备工作</strong></h1><ul><li>C++ 预备知识<br>（我假设你正在学习 C ++，即将开始学习，或者只是为了好玩而阅读。本文不是 C ++ 101 教程——需要读者对 C ++ 已经有一些了解。）</li><li>Visual Studio Code 编辑器<br><a href="https://code.visualstudio.com/#alt-downloads">点击此处</a>下载和阅读配置文档：<a href="https://code.visualstudio.com/docs/?dv=win">Windows</a>，<a href="https://code.visualstudio.com/docs/?dv=linux64_deb">Linux</a>，<a href="https://code.visualstudio.com/docs/?dv=osx">Mac</a></li><li><strong><strong>网络连接</strong>（<strong>!important</strong>）</strong></li></ul><h3 id="--1">免责声明！</h3><p>在本文中，我将始终使用 Windows 操作系统，但是我将提供一些资源的链接，这些资源将对那些使用其他操作系统的人有所帮助。</p><p>现在开始吧！</p><h1 id="-c-"><strong>下载和安装 C++ 编译器</strong></h1><ul><li>访问 <a>www.mingw.org</a>，点击 “Download/Installer”，下载 MinGW 配置文件。<a href="https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe/">Windows 电脑下载链接</a>，<a href="http://www.mingw.org/wiki/LinuxCrossMinGW">Linux 电脑下载链接</a>，<a href="https://brewinstall.org/Install-mingw-w64-on-Mac-with-Brew/">下载链接</a>。</li></ul><blockquote>MinGW 是 “Windows 的 Minimallist GNU” 的缩写，是 Microsoft Windows 的本地简易开发环境。</blockquote><ul><li>下载后，安装 MinGW，等待显示 “MinGW Installation Manager”。</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2019/10/Capture1.png" class="kg-image" alt="Capture1" width="600" height="400" loading="lazy"></figure><ul><li>当屏幕显示 “MinGW Installation Manager”时，点击 <code>mingw32-gcc-g++</code>，然后选择 “Mark for Installation”。</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2019/10/Capture2.png" class="kg-image" alt="Capture2" width="600" height="400" loading="lazy"></figure><ul><li>在左上角菜单中，点击 “Installation &nbsp;&gt; Apply Changes”。</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2019/10/Capture3.png" class="kg-image" alt="Capture3" width="600" height="400" loading="lazy"></figure><ul><li>等待完全安装好。在此过程中，请确保你具有稳定的 Internet 连接。</li></ul><h1 id="-path-c-">编辑 PATH 环境变量，包含一个存放 C ++ 编译器的目录</h1><p>PATH 是类 Unix 操作系统、DOS、OS/2 和 Microsoft Windows上的环境变量，它指定可执行程序所在的目录集。通常，每个执行进程或用户会话都有其自己的 PATH 设置。——<a href="https://en.wikipedia.org/wiki/PATH_(variable)">维基百科</a></p><p>安装好 MinGW 后，可以在 C：\ MinGW \ bin 中找到它。现在，你必须将此目录包括在环境变量 PATH 中。如果你已经使用计算机一段时间，那么你应该已经知道该怎么做了。但是如果你不了解，那么这里有一些资源：</p><ul><li><a href="https://www.computerhope.com/issues/ch000549.htm">Windows OS 上的操作指南</a></li><li><a href="https://www.cyberciti.biz/faq/unix-linux-adding-path/">Linux 上的操作指南</a></li><li><a href="https://hathaway.cc/2008/06/how-to-edit-your-path-environment-variables-on-mac/">Mac OS 上的操作指南</a></li></ul><h1 id="-vs-code-code-runner-">在 VS Code 中安装 Code Runner 扩展</h1><p>现在我们已经设置好编译器，来安装 Code Runner 吧。</p><p>使用 Code Runner，你可以运行多种语言的代码段或代码文件：</p><blockquote>C, C++, Java, JavaScript, PHP, Python, Perl, Perl 6, Ruby, Go, Lua, Groovy, PowerShell, BAT/CMD, BASH/SH, F# Script, F# (.NET Core), C# Script, C# (.NET Core), VBScript, TypeScript, CoffeeScript, Scala, Swift, Julia, Crystal, OCaml Script, R, AppleScript, Elixir, Visual Basic .NET, Clojure, Haxe, Objective-C, Rust, Racket, AutoHotkey, AutoIt, Kotlin, Dart, Free Pascal, Haskell, Nim, D, Lisp, Kit, 及自定义命令.</blockquote><ul><li>点击<a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner">此处</a>下载</li><li>或在 “Vs Code 市场”选项卡中搜索</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2019/10/Capture4.png" class="kg-image" alt="Capture4" width="600" height="400" loading="lazy"></figure><ul><li>下载之后重启 Vs Code</li><li>在 Vs Code 打开你的 C++ 文件。以下是一个最简单的代码示例：</li></ul><pre><code class="language-c">#include &lt;iostream&gt;
using namespace std;
int main() 
{
    cout &lt;&lt; "Hello world!";
    return 0;
}
</code></pre><p>将此文件保存为 <code>test.cpp</code>。</p><h1 id="-code-runner-"><strong>用 Code Runner 运行代码</strong></h1><ul><li>使用快捷方式 <code>Ctrl+Alt+N</code></li><li>或者按 F1 键，选择 /type Run Code</li><li>或者右击文本编辑器，在菜单中点击 Run Code</li></ul><p>代码运行后，输出将显示在“输出”窗口中。使用 Ctrl + 快捷方式打开输出窗口。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2019/10/Capture5.png" class="kg-image" alt="Capture5" width="600" height="400" loading="lazy"></figure><h1 id="--2"><strong>停止运行代码</strong></h1><ul><li>使用快捷方式 <code>Ctrl+Alt+M</code></li><li>或者按 F1 键，选择 /type Stop Code Run</li><li>或者右击输出，在菜单中点击 Stop Code Run</li></ul><p>好棒！你成功在 Vs Code 中设置了 C++ 环境！</p><h1 id="--3"><strong>总结</strong></h1><p>快速提示：默认情况下，Vs Code 的输出终端为只读。如果你正在运行需要用户输入的代码，例如：</p><pre><code class="language-c">#include &lt;iostream&gt;
using namespace std;

const double pi = 3.14159; 

void calculate()
{
  double area; 
  double radius;

  cout&lt;&lt;"Enter Radius: "&lt;&lt;endl; 
  cin&gt;&gt;radius;

  area = pi * radius * radius; 

  cout&lt;&lt;"area is: "&lt;&lt;area&lt;&lt;endl;
 }
 
int main()
{
  calculate(); 
  return 0;
}
</code></pre><p>你将不能在终端输入内容， <code>Cannot edit in read-only terminal</code>。<br>你需要启动 读-写，来解决这个问题。</p><ul><li>在 Vs Code 中，点击 Go to File &gt; Preference &gt; Setting</li><li>在左边面板的用户选项卡，找到扩展部分</li><li>滚动找到 “Run Code Configuration”</li><li>滚动找到一个复选框 <code>Run in Terminal</code>（是否在集成终端中运行代码），选中它</li></ul><p>或者</p><ul><li>在 <code>setting.json</code> 文件中，添加：</li></ul><pre><code>"code-runner.runInTerminal": true
</code></pre><p>恭喜，你完成所有步骤了！</p><h1 id="c-"><strong>C++ 资源</strong></h1><p>你可以通过下列资源，开始学习 C++：</p><ul><li><a href="https://www.learncpp.com/">https://www.learncpp.com/</a></li><li><a href="https://www.codecademy.com/learn/learn-c-plus-plus">https://www.codecademy.com/learn/learn-c-plus-plus</a></li><li><a href="https://www.udemy.com/free-learn-c-tutorial-beginners/">https://www.udemy.com/free-learn-c-tutorial-beginners/</a></li><li><a href="https://www.sololearn.com/Course/CPlusPlus/">https://www.sololearn.com/Course/CPlusPlus/</a></li><li><a href="https://www.youtube.com/watch?v=vLnPwxZdW4Y">https://www.youtube.com/watch?v=vLnPwxZdW4Y</a></li><li><a href="https://www.tutorialspoint.com/cplusplus/cpp_useful_resources.htm">https://www.tutorialspoint.com/cplusplus/cpp_useful_resources.htm</a></li><li><a href="https://www.tutorialspoint.com/cplusplus/cpp_useful_resources.htm">https://makeawebsitehub.com/learning-c/</a></li></ul><h1 id="credits"><strong>Credits</strong></h1><ul><li><a href="http://www.mingw.org/">MinGW Project</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner">Code Runner</a> by <a href="https://marketplace.visualstudio.com/publishers/formulahendry">Jun Han</a></li></ul><p>感谢阅读！</p><p>原文：<a href="https://www.freecodecamp.org/news/how-to-compile-your-c-code-in-visual-studio-code/">How to compile your C++ code in Visual Studio Code</a>，作者：<a href="https://www.freecodecamp.org/news/author/bolajiayodeji/">Bolaji Ayodeji</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ C 语言入门手册：几小时内就能学会的 C 语言基础 ]]>
                </title>
                <description>
                    <![CDATA[ 本手册遵循二八定律。你将在 20% 的时间内学习 80% 的 C 编程语言。 这种方式将会让你对这门语言有一个全面的认识。 本手册并不会尝试覆盖与 C 有关的一切。它只会关注这门语言的核心部分，尽量将更加复杂的主题简单化。 提示：你可以从这里获得这本手册的 PDF 或 ePub 版本 [https://flaviocopes.com/page/c-handbook/]。 尽情享受吧！ 目录  1.  C 语言简介  2.  变量与类型  3.  常量  4.  运算符  5.  条件语句  6.  循环  7.  数组  8. ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-c-beginners-handbook/</link>
                <guid isPermaLink="false">60331fa6c354c605689ea5b1</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nicholas Zhan ]]>
                </dc:creator>
                <pubDate>Mon, 22 Feb 2021 03:09:20 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/02/coverc-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>本手册遵循二八定律。你将在 20% 的时间内学习 80% 的 C 编程语言。</p>
<p>这种方式将会让你对这门语言有一个全面的认识。</p>
<p>本手册并不会尝试覆盖与 C 有关的一切。它只会关注这门语言的核心部分，尽量将更加复杂的主题简单化。</p>
<p>提示：<a href="https://flaviocopes.com/page/c-handbook/">你可以从这里获得这本手册的 PDF 或 ePub 版本</a>。</p>
<p>尽情享受吧！</p>
<h2 id="">目录</h2>
<ol>
<li><a href="#introduction-to-c">C 语言简介</a></li>
<li><a href="#variables-and-types">变量与类型</a></li>
<li><a href="#constants">常量</a></li>
<li><a href="#operators">运算符</a></li>
<li><a href="#conditionals">条件语句</a></li>
<li><a href="#loops">循环</a></li>
<li><a href="#arrays">数组</a></li>
<li><a href="#strings">字符串</a></li>
<li><a href="#pointers">指针</a></li>
<li><a href="#functions">函数</a></li>
<li><a href="#input-and-output">输入与输出</a></li>
<li><a href="#variable-scope">变量作用域</a></li>
<li><a href="#static-variables">静态变量</a></li>
<li><a href="#global-variables">全局变量</a></li>
<li><a href="#type-definitions">类型定义</a></li>
<li><a href="#enumerated-types">枚举类型</a></li>
<li><a href="#structures">结构体</a></li>
<li><a href="#command-line-parameters">命令行参数</a></li>
<li><a href="#header-files">头文件</a></li>
<li><a href="#the-preprocessor">预处理器</a></li>
<li><a href="#conclusion">结语</a></li>
</ol>
<h2 id="introduction-to-c">C 语言简介</h2>
<p>C 可能是最广为人知的编程语言。它被全世界的计算机科学课程中用作参考语言，除了 Python 与 Java，它可能是人们在学校学得最多得编程语言。</p>
<p>我记得它是我在 Pascal 之后的第二门编程语言。</p>
<p>学生们用 C 来学习编程，但它的作用远不止这一点。它不是一门学术型语言。它不是最简单的语言，因为 C 是一门非常底层的编程语言。</p>
<p>今天，C 在嵌入式设备中广泛使用，它驱动着绝大多数用 Linux 搭建的因特网服务器。Linux 内核是用 C 写的，这也意味着 C 驱动着所有安卓设备的内核。可以这么说，此时此刻，整个世界的一大部分就是由 C 代码运行的，令人惊叹。</p>
<p>在诞生之初，C 被认为是一门高级语言，因为它可以在不同机器之间移植。如今，我们或多或少都认为在 Mac 或 Windows 或 Linux 运行一个程序（可能使用 Node.js 或 Python）是理所当然的。</p>
<p>在以前，完全不是这样的。C 带来了一门易于实现的语言，它的编译器可以很容易地被移植到不同的机器上。</p>
<p>我说下译器：C 是一门编译型语言，就像 Go、Java、Swift 或 Rust 一样。其它流行的语言，比如 Python、Ruby 或 JavaScript 都是解释型语言。编译型语言与解释型语言的差别是不变的：编译型语言生成的是可直接执行和分发的二进制文件。</p>
<p>C 不支持垃圾收集（garbage collection），这意味着我们必须自己管理内存。管理内存是一项复杂的任务，需要十分小心才能预防缺陷，但 C 也因此成为了嵌入式设备（例如 Arduino）编程的理想语言。</p>
<p>C 并不会隐藏下层机器的复杂性和能力。一旦知道你能做什么，你就能拥有巨大的能力。</p>
<p>现在，我想介绍第一个 C 程序，我们将会管它叫“Hello, World”。</p>
<p>hello.c</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
    printf("Hello, World!");
}
</code></pre>
<p>让我们描述一下这段程序源代码：我们首先导入了 <code>stdio</code> 库（<code>stdio</code> 表示的是标准输入输出库（standard input-output library））。</p>
<p>这个库允许我们访问输入/输出函数。</p>
<p>C 是一门内核非常小的语言，任何内核以外的部分都以库的形式提供。其中一些库由普通编程人员构建并供他人使用。另一些库被内置在编译器中，比如 <code>stdio</code> 等。</p>
<p><code>stdio</code> 库提供了 <code>prinf()</code> 函数。</p>
<p>这个函数被包裹在 <code>main()</code> 函数中，<code>main()</code> 函数是所有 C 程序的入口。</p>
<p>但是，究竟什么是函数呢？</p>
<p>函数（function）是一个例程，它接收一个或多个参数并返回一个值。</p>
<p>在 <code>main()</code> 的例子中，函数没有参数，返回一个整数。我们使用 <code>void</code> 关键字标识该参数，使用 <code>int</code> 关键字标识返回值。</p>
<p>函数有一个由花括号包裹的函数体，函数需要进行的所有操作的代码都在函数体内。</p>
<p>如你所见，<code>printf()</code> 函数的写法稍有不同。它没有定义返回值，并且我们给它传入了一个用双引号包裹的字符串。我们并没有声明参数的类型。</p>
<p>那是因为这是一个函数调用。在 <code>stdio</code> 库中的某个地方，<code>printf</code> 被定义成</p>
<pre><code class="language-c">int printf(const char *format, ...);

</code></pre>
<p>你现在不需要理解这是何含义，简单来说，这是就是函数定义。当我们调用 <code>printf("Hello, World!");</code> 时，这就是该函数运行的地方。</p>
<p>我们在上面定义的 <code>main()</code> 函数：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
    printf("Hello, World!");
}
</code></pre>
<p>将会在程序被执行的时候由操作系统运行。</p>
<p>我们如何执行一个 C 程序呢？</p>
<p>如我所说，C 是一门编译型语言。要运行程序，我们必须先编译它。任何 Linux 或 macOS 计算机都自带了 C 编译器。至于 Windows，你可以使用适用于 Linux 的 Windows 子系统（WSL）。</p>
<p>无论如何，你都可以在打开终端时输入 <code>gcc</code>，这个命令应该会返回一个错误，提示你没有声明任何文件：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.10.50.png" alt="Screen-Shot-2020-01-29-at-10.10.50" width="600" height="400" loading="lazy"></p>
<p>很好。它说明 C 编译器是有的，现在我们可以开始使用它了。</p>
<p>现在将上面的程序输入到一个名为 <code>hello.c</code> 的文件中。你可以使用任何编辑器，不过为了简单起见，我将在命令行中使用 <code>nano</code> 编辑器：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.11.39.png" alt="Screen-Shot-2020-01-29-at-10.11.39" width="600" height="400" loading="lazy"></p>
<p>输入程序：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.16.52.png" alt="Screen-Shot-2020-01-29-at-10.16.52" width="600" height="400" loading="lazy"></p>
<p>现在按 <code>ctrl-X</code> 退出：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.18.11.png" alt="Screen-Shot-2020-01-29-at-10.18.11" width="600" height="400" loading="lazy"></p>
<p>按 <code>y</code> 键确认，然后按回车键确认文件名：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.18.15.png" alt="Screen-Shot-2020-01-29-at-10.18.15" width="600" height="400" loading="lazy"></p>
<p>就是这样，我们现在应该已经回到终端了：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.13.46.png" alt="Screen-Shot-2020-01-29-at-10.13.46" width="600" height="400" loading="lazy"></p>
<p>现在输入</p>
<pre><code class="language-sh">gcc hello.c -o hello

</code></pre>
<p>程序应该不会给你任何错误信息：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.16.31.png" alt="Screen-Shot-2020-01-29-at-10.16.31" width="600" height="400" loading="lazy"></p>
<p>但是它应该已经生成了一个名为 <code>hello</code> 的可执行程序。现在输入</p>
<pre><code class="language-sh">./hello

</code></pre>
<p>运行它：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.19.20.png" alt="Screen-Shot-2020-01-29-at-10.19.20" width="600" height="400" loading="lazy"></p>
<p>我在程序名的前面加了 <code>./</code>，告诉终端要执行的命令就在当前目录下。</p>
<p>太棒了！</p>
<p>现在，如果你调用 <code>ls -al hello</code>，你能看到这个程序只有 12KB 大：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-01-29-at-10.19.55.png" alt="Screen-Shot-2020-01-29-at-10.19.55" width="600" height="400" loading="lazy"></p>
<p>这是 C 的优点之一：它是高度优化的，这也是它非常适用于资源非常有限的嵌入式设备的原因之一。</p>
<h2 id="variables-and-types">变量与类型</h2>
<p>C 是一门静态类型语言。</p>
<p>这意味着任何变量都有一个相关联的类型，并且该类型在编译时是可知的。</p>
<p>这与你在 Python、JavaScript、PHP 和其它解释型语言中使用变量的方式大有不同。</p>
<p>当你在 C 中创建变量时，你必须在声明中给出该变量的类型。</p>
<p>在这个示例中，我们初始化一个 <code>int</code> 类型的变量 <code>age</code>：</p>
<pre><code class="language-c">int age;

</code></pre>
<p>变量名可以包含任意大写或小写字母，也可以包含数字和下划线，但是不能以数字开头。<code>AGE</code> 和 <code>Age10</code> 都是有效的变量名，但 <code>1age</code> 就不是了。</p>
<p>你还可以在声明中初始化变量，给出初始值即可：</p>
<pre><code class="language-c">int age = 37;

</code></pre>
<p>变量一旦声明，你就可以在程序代码中使用它了。你在任何时候都可以使用 <code>=</code> 改变它的值，例如 <code>age = 100;</code>（提供的新值的类型与原值相同）。</p>
<p>在这种情况下：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
    int age = 0;
    age = 37.2;
    printf("%u", age);
}
</code></pre>
<p>编译器会在编译时发出警告，然后将小数转为整数。</p>
<p><a href="http://localhost:4000/c-introduction">C</a> 的内置数据类型有 <code>int</code>、<code>char</code>、<code>short</code>、<code>long</code>、<code>float</code>、<code>double</code>、<code>long double</code>。咱们进一步了解这些数据类型吧。</p>
<h3 id="">整数</h3>
<p>C 给我们提供了下列定义整数的类型：</p>
<ul>
<li><code>char</code></li>
<li><code>int</code></li>
<li><code>short</code></li>
<li><code>long</code></li>
</ul>
<p>通常，你很可能会使用 <code>int</code> 保存整数。但是在某些情况下，你或许想在其它三个选项中选取合适的类型。</p>
<p><code>char</code> 类型通常被用来保存 ASCII 表中的字母，但是它也可以用来保存 <code>-128</code> 到 <code>127</code> 之间的小整数。它占据至少一个字节。</p>
<p><code>int</code> 占据至少两个字节。<code>short</code> 占据至少两个字节。<code>long</code> 占据至少四个字节。</p>
<p>如你所见，我们并不保证不同环境下的值相同。我们只有一个指示。问题在于每种数据类型中所存储的具体值是由实现和系统架构决定的。</p>
<p>我们保证 <code>short</code> 不会比 <code>int</code> 长。并且我们还保证 <code>long</code> 不会比 <code>int</code> 短。</p>
<p>ANSI C 规范标准确定了每种类型的最小值，多亏了它，我们至少可以知道使用某个类型时可以期待的最小值。</p>
<p>如果你正在 Arduino 上用 C 编程，不同的板子上的限制会有所不同。</p>
<p>在 Arduino Uno 开发板上，<code>int</code> 占两个字节，范围从 <code>-32,768</code> 到 <code>32,767</code>。在 Arduino MKR 1010 上，<code>int</code> 占四个字节，范围从 <code>-2,147,483,648</code> 到 <code>2,147,483,647</code>。差异还真不小。</p>
<p>在所有的 Arduino 开发板上，<code>short</code> 都占两个字节，范围从 <code>-32,768</code> 到 <code>32,767</code>。<code>long</code> 占四个字节，范围从 <code>-2,147,483,648</code>  到  <code>2,147,483,647</code>。</p>
<h3 id="">无符号整数</h3>
<p>对于以上所有的数据类型，我们都可以在其前面追加一个 <code>unsigned</code>。这样一来，值的范围就不再从负数开始，而是从 0 开始。这在很多情况下是很有用的。</p>
<ul>
<li><code>unsigned char</code> 的范围从 <code>0</code> 开始，至少到 <code>255</code></li>
<li><code>unsigned int</code> 的范围从 <code>0</code> 开始，至少到 <code>65,535</code></li>
<li><code>unsigned short</code> 的范围从 <code>0</code> 开始，至少到 <code>65,535</code></li>
<li><code>unsigned long</code> 的范围从 <code>0</code> 开始，至少到 <code>4,294,967,295</code></li>
</ul>
<h3 id="">溢出的问题</h3>
<p>鉴于所有这些限制，可能会出现一个问题：我们如何确保数字不超过限制？如果超过了限制会怎样？</p>
<p>如果你有一个值为 255 的 <code>unsigned int</code>，自增返回的值为 256，这在意料之中。如果你有一个值为 255 的 <code>unsigned char</code>，你得到的结果就是 0。它重置为了初始值。</p>
<p>如果你有一个值为 255 的 <code>unsigned char</code>，给它加上 10 会得到数字 <code>9</code>：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  unsigned char j = 255;
  j = j + 10;
  printf("%u", j); /* 9 */
}
</code></pre>
<blockquote>
<p>If you don't have a signed value, the behavior is undefined.<br>
原文这里可能是 typo，从代码来看，这里描述的是有符号整数的溢出行为。</p>
</blockquote>
<p>如果你的值是有符号的，程序的行为则是未知的。程序基本上会给你一个很大的值，这个值可能变化，就像这样：</p>
<pre><code class="language-c">include &lt;stdio.h&gt;

int main(void) {
  char j = 127;
  j = j + 10;
  printf("%u", j); /* 4294967177 */
}
</code></pre>
<p>换句话说，C 并不会在你超出类型的限制时保护你。对于这种情况，你需要自己当心。</p>
<h3 id="">声明错误类型时的警告</h3>
<p>如果你声明变量并用错误的值进行初始化，<code>gcc</code> 编译器（你可能正在使用这个编译器）应该会发出警告：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  char j = 1000;
}
</code></pre>
<pre><code>hello.c:4:11: warning: implicit conversion 
  from 'int' to
      'char' changes value from 1000 to -24
      [-Wconstant-conversion]
        char j = 1000;
             ~   ^~
1 warning generated.

</code></pre>
<p>如果你直接赋值，也会有警告：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  char j;
  j = 1000;
}

</code></pre>
<p>但是对值进行增加操作（例如，使用 <code>+=</code>）就不会有警告：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  char j = 0;
  j += 1000;
}
</code></pre>
<h3 id="">浮点数</h3>
<p>浮点类型可以表示的数值范围比整数大得多，还可以表示整数无法表示的分数。</p>
<p>使用浮点数时，我们将数表示成小数乘以 10 的幂。</p>
<p>你可能见过浮点数被写成</p>
<ul>
<li><code>1.29e-3</code></li>
<li><code>-2.3e+5</code></li>
</ul>
<p>和其它的一些看起来很奇怪的形式。</p>
<p>下面的几种类型：</p>
<ul>
<li><code>float</code></li>
<li><code>double</code></li>
<li><code>long double</code></li>
</ul>
<p>是用来表示带有小数点的数字（浮点类型）的。这几种类型都可以表示正数和负数。</p>
<p>任何 C 的实现都必须满足的最小要求是 <code>float</code> 可以表示范围在 10^-37 到 10^+37 之间的数，这通常用 32 位比特实现。 <code>double</code> 可以表示一组更大范围的数，<code>long double</code> 可以保存的数还要更多。</p>
<p>与整数一样，浮点数的确切值取决于具体实现。</p>
<p>在现代的 Mac 上，<code>float</code> 用 32 位表示，精度为 24 个有效位，剩余 8 位被用来编码指数部分。</p>
<p><code>double</code> 用 64 位表示，精度为 53 个有效位，剩余 11 为用于编码指数部分。</p>
<p><code>long double</code> 类型用 80 位表示，精度为 64 位有效位，剩余 15 位被用来编码指数部分。</p>
<p>你如何能在自己的计算机上确定这些类型的大小呢？你可以写一个程序来干这事儿：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  printf("char size: %lu bytes\n", sizeof(char));
  printf("int size: %lu bytes\n", sizeof(int));
  printf("short size: %lu bytes\n", sizeof(short));
  printf("long size: %lu bytes\n", sizeof(long));
  printf("float size: %lu bytes\n", sizeof(float));
  printf("double size: %lu bytes\n", sizeof(double));
  printf("long double size: %lu bytes\n", sizeof(long double));
}
</code></pre>
<p>在我的系统上（一台现代 Mac），输出如下：</p>
<pre><code>char size: 1 bytes
int size: 4 bytes
short size: 2 bytes
long size: 8 bytes
float size: 4 bytes
double size: 8 bytes
long double size: 16 bytes

</code></pre>
<h2 id="constants">常量</h2>
<p>咱们现在来谈谈常量。</p>
<p>常量的声明与变量类似，不同之处在于常量声明的前面带有 <code>const</code> 关键字，并且你总是需要给常量指定一个值。</p>
<p>就像这样：</p>
<pre><code class="language-c">const int age = 37;

</code></pre>
<p>这在 C 中是完全有效的，尽管通常情况下将常量声明为大写，就像这样：</p>
<pre><code class="language-c">const int AGE = 37;

</code></pre>
<p>虽然这只是一个惯例，但是在你阅读或编写 C 程序时，他能给你提供巨大的帮助，因为它提高了可读性。大写的名字意味着常量，小写的名字意味着变量。</p>
<p>常量的命名规则与变量相同：可以包含任意大小写字母、数字和下划线，但是不能以数字开头。<code>AGE</code> 和 <code>Age10</code> 都是有效的变量名，而 <code>1AGE</code> 就不是了。</p>
<p>另一种定义常量的方式是使用这种语法：</p>
<pre><code class="language-c">#define AGE 37

</code></pre>
<p>在这种情况下，你不需要添加类型，也不需要使用等于符号 <code>=</code>，并且可以省略末尾的分号。</p>
<p>C 编译器将会在编译时从声明的值推断出相应的类型。</p>
<h2 id="operators">运算符</h2>
<p>C 给我们提供了各种各样的运算符，我们可以用来操作数据。</p>
<p>特别地，我们可以识别不同分组的运算符：</p>
<ul>
<li>算术运算符</li>
<li>比较运算符</li>
<li>逻辑运算符</li>
<li>复合赋值运算符</li>
<li>位运算符</li>
<li>指针运算符</li>
<li>结构运算符</li>
<li>混合运算符</li>
</ul>
<p>在这一节中，我们将用两个假想的变量 <code>a</code> 和 <code>b</code> 举例，详细介绍所有这些运算符。</p>
<p>为了简单起见，我将不会介绍位运算符、结构运算符和指针运算符。</p>
<h3 id="">算术运算符</h3>
<p>我将把这个小型分组分为二元运算符和一元运算符。</p>
<p>二元操作符需要两个操作数：</p>
<table>
<thead>
<tr>
<th>操作符</th>
<th>名字</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>=</code></td>
<td>赋值</td>
<td><code>a = b</code></td>
</tr>
<tr>
<td><code>+</code></td>
<td>加</td>
<td><code>a + b</code></td>
</tr>
<tr>
<td><code>-</code></td>
<td>减</td>
<td><code>a - b</code></td>
</tr>
<tr>
<td><code>*</code></td>
<td>乘</td>
<td><code>a * b</code></td>
</tr>
<tr>
<td><code>/</code></td>
<td>除</td>
<td><code>a / b</code></td>
</tr>
<tr>
<td><code>%</code></td>
<td>取模</td>
<td><code>a % b</code></td>
</tr>
</tbody>
</table>
<p>一元运算符只需要一个操作数：</p>
<table>
<thead>
<tr>
<th>运算符</th>
<th>名字</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>+</code></td>
<td>一元加</td>
<td><code>+a</code></td>
</tr>
<tr>
<td><code>-</code></td>
<td>一元减</td>
<td><code>-a</code></td>
</tr>
<tr>
<td><code>++</code></td>
<td>自增</td>
<td><code>a++</code>  or  <code>++a</code></td>
</tr>
<tr>
<td><code>--</code></td>
<td>自减</td>
<td><code>a--</code>  or  <code>--a</code></td>
</tr>
</tbody>
</table>
<p><code>a++</code> 与 <code>++a</code> 的区别在于：<code>a++</code> 在使用 <code>a</code> 之后才自增它的值，而 <code>++a</code> 会在使用 <code>a</code> 之前自增它的值。</p>
<p>例如：</p>
<pre><code class="language-c">int a = 2;
int b;
b = a++ /* b 为 2，a 为 3 */
b = ++a /* b 为 4，a 为 4 */

</code></pre>
<p>这也适用于递减运算符。</p>
<h3 id="">比较运算符</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>名字</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>==</code></td>
<td>相等</td>
<td><code>a == b</code></td>
</tr>
<tr>
<td><code>!=</code></td>
<td>不相等</td>
<td><code>a != b</code></td>
</tr>
<tr>
<td><code>&gt;</code></td>
<td>大于</td>
<td><code>a &gt; b</code></td>
</tr>
<tr>
<td><code>&lt;</code></td>
<td>小于</td>
<td><code>a &lt; b</code></td>
</tr>
<tr>
<td><code>&gt;=</code></td>
<td>大于等于</td>
<td><code>a &gt;= b</code></td>
</tr>
<tr>
<td><code>&lt;=</code></td>
<td>小于等于</td>
<td><code>a &lt;= b</code></td>
</tr>
</tbody>
</table>
<h3 id="">逻辑运算符</h3>
<ul>
<li><code>!</code> 非（例如：<code>!a</code>）</li>
<li><code>&amp;&amp;</code> 与（例如：<code>a &amp;&amp; b</code>）</li>
<li><code>||</code> 或（例如：<code>a || b</code>）</li>
</ul>
<p>这些运算符在使用布尔值时非常有用。</p>
<h3 id="">复合赋值运算符</h3>
<p>当赋值与算术运算同时进行时，这些运算符非常有用。</p>
<table>
<thead>
<tr>
<th>运算符</th>
<th>名字</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>+=</code></td>
<td>加且赋值</td>
<td><code>a += b</code></td>
</tr>
<tr>
<td><code>-=</code></td>
<td>减且赋值</td>
<td><code>a -= b</code></td>
</tr>
<tr>
<td><code>*=</code></td>
<td>乘且赋值</td>
<td><code>a *= b</code></td>
</tr>
<tr>
<td><code>/=</code></td>
<td>除且赋值</td>
<td><code>a /= b</code></td>
</tr>
<tr>
<td><code>%=</code></td>
<td>求模且赋值</td>
<td><code>a %= b</code></td>
</tr>
</tbody>
</table>
<h3 id="">三目运算符</h3>
<p>三目运算符是 C 中唯一一个使用三个操作数的运算符，并且它是表达条件的简便方法。</p>
<p>它看起来长这样：</p>
<p><code>&lt;条件&gt; ? &lt;表达式&gt; : &lt;表达式&gt;</code></p>
<p>示例：</p>
<p>若 <code>a</code> 的值为 <code>true</code>，就执行语句 <code>b</code>，否则执行语句 <code>c</code>。</p>
<p>三目运算符的功能与 if/else 条件语句相同，但是它更短，还可以被内联进表达式。</p>
<h3 id="sizeof">sizeof</h3>
<p><code>sizeof</code> 运算符返回你传入的操作数的大小。你可以传入变量，或者甚至是类型也可以。</p>
<p>使用示例：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  int age = 37;
  printf("%ld\n", sizeof(age));
  printf("%ld", sizeof(int));
}
</code></pre>
<h3 id="">运算符优先级</h3>
<p>对于所有的这些运算符（以及我们还没有在本文中介绍的其它运算符，包括位运算符、结构运算符和指针运算符），我们在单个表达式中一起使用它们时必须要留意。</p>
<p>假如我们有这个运算：</p>
<pre><code class="language-c">int a = 2;
int b = 4;
int c = b + a * a / b - a;
</code></pre>
<p><code>c</code> 的值是多少？我们在执行乘和除之前有进行加法操作吗？</p>
<p>这里是给我们解惑的一组规则。</p>
<p>按照顺序，优先级从低到高：</p>
<ul>
<li>赋值运算符 <code>=</code></li>
<li>二元运算符 <code>+</code> 和 <code>-</code></li>
<li>运算符 <code>*</code> 和 <code>/</code></li>
<li>一元运算符 <code>+</code> 和 <code>-</code></li>
</ul>
<p>运算符还具有关联规则，除了一元运算符和赋值运算符之外，该规则总是从左到右的。</p>
<p>在：</p>
<pre><code class="language-c">int c = b + a * a / b - a;
</code></pre>
<p>中，我们首先执行 <code>a * a / b</code>，由于是从左到右的，我们可以拆分为 <code>a * a</code> 与其结果 <code>/b</code>：<code>2 * 2 = 4</code>，<code>4 / 4 = 1</code>。</p>
<p>然后我们可以进行加法操作和减法操作：4 + 1 - 2。<code>c</code> 的值是 <code>3</code>。</p>
<p>然而，在所有的示例中，我都想确保你意识到你可以使用括号让任何相似的表达式更易读和易理解。</p>
<p>括号的优先级比其它任何运算符都要高。</p>
<p>上述示例表达式可以被重写为：</p>
<pre><code class="language-c">int c = b + ((a * a) / b) - a;
</code></pre>
<p>并且我们不必考虑太多。</p>
<h2 id="conditionals">条件语句</h2>
<p>任何编程语言都给程序员提供了进行选择的能力。</p>
<p>我们想要在一些情况下进行 X，而在其它情况下进行 Y。</p>
<p>我们想检查数据，根据数据的状态做选择。</p>
<p>C 给我们提供了两种方式。</p>
<p>第一种方式是带 <code>else</code> 的 <code>if</code> 语句，第二种是 <code>switch</code> 语句。</p>
<h3 id="if">if</h3>
<p>在 <code>if</code> 语句中，你可以在检查到条件为 true 的时候，执行花括号内的代码块：</p>
<pre><code class="language-c">int a = 1;

if (a == 1) {
  /* 进行一些操作 */
}
</code></pre>
<p>如果原始条件的结果是 false，你可以追加一个 <code>else</code> 块以不同的代码块：</p>
<pre><code class="language-c">int a = 1;

if (a == 2) {
  /* 进行一些操作 */
} else {
  /* 进行另一些操作 */
}
</code></pre>
<p>谨防一种常见的缺陷源——总是在比较中使用比较运算符 <code>==</code>，而不是赋值运算符 <code>=</code>。如果你不这么做，除非参数为 <code>0</code>，否则 <code>if</code> 条件检查的结果将一直都是 true。例如，如果你这么做：</p>
<pre><code class="language-c">int a = 0;

if (a = 0) {
  /* 永远都不会被调用 */
}
</code></pre>
<p>为什么会这样呢？因为条件检查会寻找一个布尔类型的结果（比较的结果），数字 <code>0</code> 总是等于 false。其它的任何东西都是 true，包括负数。</p>
<p>通过将多个 <code>if</code> 语句堆叠在一起，你可以有多个 <code>else</code> 块：</p>
<pre><code class="language-c">int a = 1;

if (a == 2) {
  /* do something */
} else if (a == 1) {
  /* 进行一些操作 */
} else {
  /* 进行另一些操作 */
}
</code></pre>
<h3 id="switch">switch</h3>
<p>如果你的检查需要使用非常多的 if/else/if 块，可能是因为你需要检查变量的具体值，这时 <code>switch</code> 语句对你来说就非常有用了。</p>
<p>你可以提供一个变量作为条件，然后为期望的每个值使用一个 <code>case</code> 入口点：</p>
<pre><code class="language-c">int a = 1;

switch (a) {
  case 0:
    /* 进行一些操作 */
    break;
  case 1:
    /* 进行另一些操作 */
    break;
  case 2:
    /* 进行另一些操作 */
    break;
</code></pre>
<p>当前一个 case 执行完后，为了避免下一个 case 被执行，我们需要在每个 case 的末尾使用 <code>break</code> 关键字。这种“级联”效果在某些创造性方法中非常有用的。</p>
<p>你可以在末尾添加一个“捕获所有的” case，名为 <code>default</code>：</p>
<pre><code class="language-c">int a = 1;

switch (a) {
  case 0:
    /* 进行一些操作 */
    break;
  case 1:
    /* 进行另一些操作 */
    break;
  case 2:
    /* 进行另一些操作 */
    break;
  default:
    /* 处理所有其它的情况 */
    break;
}
</code></pre>
<h2 id="loops">循环</h2>
<p>C 给我们提供了三种循环：<strong>For 循环</strong>、<strong>while 循环</strong> 和 <strong>do while 循环</strong>。它们都允许你在数组上进行迭代，但又各有不同。咱们仔细来看一看它们。</p>
<h3 id="for">For 循环</h3>
<p>第一种执行循环是 <strong>for 循环</strong>，它可能也是最常见的循环。</p>
<p>使用 <code>for</code> 关键字时，我们可以先定义循环的 <em>规则</em>，然后提供反复执行的那个代码块。</p>
<p>就像这样：</p>
<pre><code class="language-c">for (int i = 0; i &lt;= 10; i++) {
  /* 反复执行的指令 */
}
</code></pre>
<p><code>(int i = 0; i &lt;= 10; i++)</code> 代码块包含与循环细节有关的三个部分：</p>
<ul>
<li>初始条件（<code>int i = 0</code>）</li>
<li>测试（<code>i &lt;= 10</code>）</li>
<li>增长（<code>i++</code>）</li>
</ul>
<p>我们首先定义循环变量，本示例中为 <code>i</code>。<code>i</code> 是循环中的一个常用变量名，<code>j</code> 是嵌套循环（循环内的循环）内使用的变量名。这只是一个惯例。</p>
<p>变量 <code>i</code> 的值被初始化为 0，并且第一次迭代执行完毕。然后 <code>i</code> 像增长部分（这个示例中是 <code>i++</code>，递增 1）所说的那样增长，并且所有的循环会一直重复，直到 <code>i</code> 的值达到数字 10。</p>
<p>在循环的主代码块内，我们可以访问变量 <code>i</code>，从而获知我们当前所处的是哪个迭代。这个程序应该打印 <code>0 1 2 3 4 5 5 6 7 8 9 10</code>：</p>
<pre><code class="language-c">for (int i = 0; i &lt;= 10; i++) {
  /* 反复执行的指令 */
  printf("%u ", i);
}
</code></pre>
<p>循环可以从较高的数字开始，往较低的数字逼近，就像这样：</p>
<pre><code class="language-c">for (int i = 10; i &gt; 0; i--) {
  /* 反复执行的指令 */
}
</code></pre>
<p>你也可以让循环变量的增量为 2 或者其它值：</p>
<pre><code class="language-c">for (int i = 0; i &lt; 1000; i = i + 30) {
  /* 反复执行的指令 */
}
</code></pre>
<h3 id="while">while 循环</h3>
<p><strong>while 循环</strong> 写起来比 <code>for</code> 循环要简单，因为它需要你在自己的部分做更多的事情。</p>
<p>使用 <code>while</code> 时，你只需要检查条件，而不用在循环开始时预先定义所有的循环数据（就像你在 <code>for</code> 循环中做的那样）：</p>
<pre><code class="language-c">while (i &lt; 10) {

}
</code></pre>
<p>这段代码假定 <code>i</code> 已经定义并且用某个值进行了初始化。</p>
<p>除非你在循环内的某些地方增加变量 <code>i</code> 的值，否则这个循环会变成一个 <strong>无限循环</strong>。无限循环非常糟糕，因为它会阻塞程序，从而使其它任何事情都不会发生。</p>
<p>对于一个“正确的” while 循环，这是你需要知道的：</p>
<pre><code class="language-c">int i = 0;

while (i &lt; 10) {
  /* 做点事情 */

  i++;
}
</code></pre>
<p>其中有一个例外，我们将会在一分钟后看到它。在这之前，让我介绍下 <code>do while</code>。</p>
<h3 id="dowhile">Do while 循环</h3>
<p>while 循环非常棒，但是有些时候你可能需要做某件特定的事情：你总是想执行某个代码块，然后 <em>可能</em> 一直重复它。</p>
<p>这可以通过 <code>do while</code> 关键字来完成。它在某种程度上和 <code>while</code> 循环非常类似，但是会有些许不同：</p>
<pre><code class="language-c">int i = 0;

do {
  /* 做点事情 */

  i++;
} while (i &lt; 10);
</code></pre>
<p>尽管条件检查在底部，但是包含注释 <code>/* 做点事情 */</code> 的代码块总是会至少执行一次。</p>
<p>然后，只要 <code>i</code> 小于 10，我们都将会重复这个代码块。</p>
<h3 id="break">使用 break 跳出循环</h3>
<p>在所有的 C 循环内，不管循环的条件设置得如何，我们都有一种在某个时间立即跳出循环的方法。</p>
<p>这是通过 <code>break</code> 关键字来完成的。</p>
<p>这在很多情况下非常有用，你可能想检查某个变量的值，例如：</p>
<pre><code class="language-c">for (int i = 0; i &lt;= 10; i++) {
  if (i == 4 &amp;&amp; someVariable == 10) {
    break;
  }
}
</code></pre>
<p>对 <code>while</code> 循环（也适用于 <code>do while</code> 循环）来说，使用这种方式跳出循环非常有趣，因为我们可以创建一个看似无限的循环，不过我们可以在某个条件发生时结束这个循环。你可以在循环代码块里面定义它：</p>
<pre><code class="language-c">int i = 0;
while (1) {
  /* 做点事情 */

  i++;
  if (i == 10) break;
}
</code></pre>
<p>这种循环在 C 中非常普遍。</p>
<h2 id="arrays">数组</h2>
<p>数组是存储多个变量的变量。</p>
<p>在 C 中，数组中的每个值都必须有 <strong>相同的类型</strong>。这意味着你将会有 <code>int</code> 值组成的数组， <code>double</code> 值组成的数组，等等。</p>
<p>你可以像这样定义一个 <code>int</code> 型的数组：</p>
<pre><code class="language-c">int prices[5];
</code></pre>
<p>你必须总是声明数组的大小。C 没有提供开箱即用的动态数组（为此，你必须使用像链表这样的数据结构）。</p>
<p>你可以使用常量定义数组的大小：</p>
<pre><code class="language-c">const int SIZE = 5;
int prices[SIZE];
</code></pre>
<p>你可以在定义数组的时候进行初始化，就像这样：</p>
<pre><code class="language-c">int prices[5] = { 1, 2, 3, 4, 5 };
</code></pre>
<p>但是你也可以在定义数组之后为其赋值，用这种方式：</p>
<pre><code class="language-c">int prices[5];

prices[0] = 1;
prices[1] = 2;
prices[2] = 3;
prices[3] = 4;
prices[4] = 5;
</code></pre>
<p>或者使用循环，这更加实际：</p>
<pre><code class="language-c">int prices[5];

for (int i = 0; i &lt; 5; i++) {
  prices[i] = i + 1;
}
</code></pre>
<pre><code class="language-c">prices[0]; /* 第一个数组项的值 */
prices[1]; /* 第二个数组项的值 */
</code></pre>
<p>数组的索引从 0 开始，所以一个有五个元素的数组，比如上面的 <code>prices</code> 数组，将会包含的数组项的范围为 <code>prices[0]</code> 到 <code>prices[4]</code>。</p>
<p>有趣的是，C 数组中的所有元素都是顺序存放的，一个接一个。高级编程语言通常不会出现这种情况。</p>
<p>另一件有趣的事情是：数组的变量名，上述示例中的 <code>prices</code>，是一个指向数组中首个元素的 <strong>指针</strong>。因此，可以像普通指针一样使用数组。</p>
<p>稍后会介绍更多有关指针的内容。</p>
<h2 id="strings">字符串</h2>
<p>在 C 中，字符串是一种特殊的数组：字符串是由 <code>char</code> 值组成的数组：</p>
<pre><code class="language-c">char name[7];
</code></pre>
<p>我在介绍 C 中的数据类型时介绍过 <code>char</code> 类型，但是简而言之，它通常用于存储 ASCII 表中的字母。</p>
<p>可以像初始化一个普通的数组那样初始化一个字符串：</p>
<pre><code class="language-c">char name[7] = { "F", "l", "a", "v", "i", "o" };
</code></pre>
<p>或者使用更加方便的字符串字面量（也被称为字符串常量），一组用双引号引起来的字符：</p>
<pre><code class="language-c">char name[7] = "Flavio";
</code></pre>
<p>你可以通过 <code>printf()</code> 打印字符串，使用 <code>%s</code>：</p>
<pre><code class="language-c">printf("%s", name);
</code></pre>
<p>你有注意到“Flavio”是 6 个字符长，但是我定义了一个长度为 7 的数组吗？这是因为字符串中的最后一个字符必须是 <code>0</code>，它是字符串的终止符号，我们必须给它留个位置。</p>
<p>记住这个非常重要，尤其是当你操作字符串的时候。</p>
<p>说到操作字符串，C 提供了一个非常重要的标准库：<code>string.h</code>。</p>
<p>这个库是必不可少的，因为它抽象了很多与字符串有关的底层细节，给我们提供了一组非常有用的函数。</p>
<p>你可以在程序中加载这个库，需要在文件顶部加上：</p>
<pre><code class="language-c">#include &lt;string.h&gt;
</code></pre>
<p>一旦你这么做了之后，你就可以访问函数：</p>
<ul>
<li><code>strcpy()</code>：将一个字符串复制到另一个字符串</li>
<li><code>strcat()</code>：将一个字符串追加到另一个字符串</li>
<li><code>strcmp()</code>：比较两个字符串是否相等</li>
<li><code>strncmp()</code>：比较两个字符串的前 <code>n</code> 个字符</li>
<li><code>strlen()</code>：计算字符串的长度</li>
</ul>
<p>还有很多很多其它的函数供你调用。</p>
<h2 id="pointers">指针</h2>
<p>在我看来，指针是 C 中最令人不解/最具挑战的部分。尤其当你是编程新手的时候，如果你是从像 Python 或 JavaScript 这样的高级语言来到 C 的，也会这样。</p>
<p>在这一节中，我想以最简单但又不模糊的方式介绍它们。</p>
<p>指针是某个内存块的地址，这个内存块包含一个变量。</p>
<p>当你像这样声明一个整数时：</p>
<pre><code class="language-c">int age = 37;
</code></pre>
<p>我们可以使用 <code>&amp;</code> 运算符获取内存中该变量的地址值：</p>
<pre><code class="language-c">printf("%p", &amp;age); /* 0x7ffeef7dcb9c */
</code></pre>
<p>我在 <code>printf()</code> 内声明 <code>%p</code> 格式来打印地址值。</p>
<p>我们可以将该地址赋给一个变量：</p>
<pre><code class="language-c">int address = &amp;age;
</code></pre>
<p>当在声明中使用 <code>int *address</code> 时，我们并没有在声明一个整数值，而是在声明一个 <strong>指向一个整数的指针</strong>。</p>
<p>我们可以使用指针运算符获取该地址指向的变量的值：</p>
<pre><code class="language-c">int age = 37;
int *address = &amp;age;
printf("%u", *address); /* 37 */
</code></pre>
<p>我们又一次使用指针运算符，但是由于这次它不是一个声明，所以它表示“该指针指向的变量的值”。</p>
<p>在这个示例中，我们声明了一个 <code>age</code> 变量，但是我们使用了一个指针来初始化它的值：</p>
<pre><code class="language-c">int age;
int *address = &amp;age;
*address = 37;
printf("%u", *address);
</code></pre>
<p>在使用 C 时，你会发现很多东西都建立在这个简单的概念之上。所以自己运行一下上面的示例，确保你对它有所熟悉。</p>
<p>指针是一个非常好的机会，因为它们迫使我们考虑内存地址以及数据是如何组织的。</p>
<p>数组就是一个例子。当你声明一个数组时：</p>
<pre><code class="language-c">int prices[3] = { 5, 4, 3 };
</code></pre>
<p><code>prices</code> 变量实际上是一个指向数组首个元素的指针。在这种情况下，你可以使用这个 <code>printf()</code> 函数获取第一个数组元素的值：</p>
<pre><code class="language-c">printf("%u", *prices); /* 5 */
</code></pre>
<p>我们可以通过给 <code>prices</code> 指针加一来获取第二个元素，这是一件非常酷的事情：</p>
<pre><code class="language-c">printf("%u",`_ `(prices + 1)); /* 4 */
</code></pre>
<p>这种做法对于所有的其它值也适用。</p>
<p>我们还可以进行很多非常美妙的字符串操作，因为字符串的底层就是数组。</p>
<p>我们还有很多其它的使用场景，包括传递对象或函数的引用，从而避免消耗更多的资源来进行复制。</p>
<h2 id="functions">函数</h2>
<p>我们通过函数将代码组织成子例程，这样就可以：</p>
<ol>
<li>给它一个名字</li>
<li>在需要它们的时候进行调用</li>
</ol>
<p>从你的第一个程序（“Hello, World!”）开始，你就在使用 C 函数了：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
    printf("Hello, World!");
}
</code></pre>
<p><code>main()</code> 函数是一个非常重要的函数，它是 C 程序的入口点。</p>
<p>这是另一个函数：</p>
<pre><code class="language-c">void doSomething(int value) {
    printf("%u", value);
}
</code></pre>
<p>函数有 4 个重要的方面：</p>
<ol>
<li>它们有一个名字，所以我们可以在之后调用它们</li>
<li>它们声明一个返回值</li>
<li>它们可以有参数</li>
<li>它们有一个函数体，用花括号包裹</li>
</ol>
<p>函数体是一组指令，任何时候，只要函数被调用，这组指令就会被执行。</p>
<p>如果函数没有返回值，你可以在函数名前面使用关键字 <code>void</code>。否则你就要声明该函数的返回值类型（整数为 <code>int</code>，浮点数为 <code>float</code>，字符串为 <code>const char *</code>，等等）。</p>
<p>函数返回值的数量不能超过一个。</p>
<p>函数可以有参数。它们是可选的。如果函数没有参数，我们就在括号内插入 <code>void</code>，就像这样：</p>
<pre><code class="language-c">void doSomething(void) {
  /* ... */
}
</code></pre>
<p>在这种情况下，当我们调用该函数时，括号内没有任何东西：</p>
<pre><code class="language-c">doSomething();
</code></pre>
<p>如果有一个参数，我们就声明该参数的类型和名字，就像这样：</p>
<pre><code class="language-c">void doSomething(int value) {
   /* ... */
}
</code></pre>
<p>当我们调用该函数时，我们会在括号内传递对应的参数，就像这样：</p>
<pre><code class="language-c">doSomething(3);
</code></pre>
<p>我们可以有多个参数，为此我们使用逗号对它们进行分隔，在声明和调用时都是这样：</p>
<pre><code class="language-c">void doSomething(int value1, int value2) {
   /* ... */
}

doSomething(3, 4);
</code></pre>
<p>参数是通过 <strong>拷贝</strong> 传递的。这意味着如果你修改 <code>value1</code>，它的值是在局部作用域内修改的。函数外的那个值，即我们在调用时传入的值，并不会改变。</p>
<p>如果你传入的参数为一个 <strong>指针</strong>，你可以修改该变量的值，因为你现在可以使用它的内存地址直接访问它。</p>
<p>你不能为参数定义默认值。C++ 是可以的（Arduino Language 程序也可以），但是 C 不行。</p>
<p>确保你在调用函数之前定义了该函数，否则编译器将会给出一个警告和一个错误：</p>
<pre><code>➜  ~ gcc hello.c -o hello; ./hello
hello.c:13:3: warning: implicit declaration of
      function 'doSomething' is invalid in C99
      [-Wimplicit-function-declaration]
  doSomething(3, 4);
  ^
hello.c:17:6: error: conflicting types for
      'doSomething'
void doSomething(int value1, char value2) {
     ^
hello.c:13:3: note: previous implicit declaration
      is here
  doSomething(3, 4);
  ^
1 warning and 1 error generated.
</code></pre>
<p>你收到的警告与顺序有关，我之前有提到过这个。</p>
<p>错误与另一件事情有关。因为 C 没有在调用函数之前没有“看到”该函数的声明，所以它必须进行假设。并且，它假设该函数返回 <code>int</code>。然而该函数返回的是 <code>void</code>，因此出现了错误。</p>
<p>如果你将该函数的定义修改为：</p>
<pre><code class="language-c">int doSomething(int value1, int value2) {
  printf("%d %d\n", value1, value2);
  return 1;
}
</code></pre>
<p>你就只会得到警告，错误消失了：</p>
<pre><code>➜  ~ gcc hello.c -o hello; ./hello
hello.c:14:3: warning: implicit declaration of
      function 'doSomething' is invalid in C99
      [-Wimplicit-function-declaration]
  doSomething(3, 4);
  ^
1 warning generated.
</code></pre>
<p>不管是何种情况，确保你在使用函数之前声明了它。要么将函数上移，要么在头文件中加入该函数的原型。</p>
<p>在函数内部，你可以声明变量：</p>
<pre><code class="language-c">void doSomething(int value) {
  int doubleValue = value * 2;
}
</code></pre>
<p>变量在调用该函数的那一刻创建，并且在函数退出的时候销毁。它对函数外面来说是不可见的。</p>
<p>在函数内部，你可以调用函数自己。这被称为 <strong>递归</strong>，它提供了特有的机会。</p>
<h2 id="input-and-output">输入与输出</h2>
<p>C 是一门小型语言，并且 C 的“内核”并不包含任何输入/输出（I/O）功能。</p>
<p>当然，这并不是 C 所独有的。语言内核与 I/O 无关是很常见的。</p>
<p>在 C 中，输入/输出由 C 的标准库通过一组定义在 <code>stdio.h</code> 头文件中的函数向我们提供。</p>
<p>你可以在 C 文件顶部使用：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
</code></pre>
<p>导入这个库。</p>
<p>这个库给我们提供了很多其它的函数：</p>
<ul>
<li><code>printf()</code></li>
<li><code>scanf()</code></li>
<li><code>sscanf()</code></li>
<li><code>fgets()</code></li>
<li><code>fprintf()</code></li>
</ul>
<p>在描述这个函数干啥之前，我想先花一分钟讲一下 <strong>I/O 流</strong>。</p>
<p>在 C 中，我们有三种类型的 I/O 流：</p>
<ul>
<li><code>stdin</code>（标准输入）</li>
<li><code>stdout</code>（标准输出）</li>
<li><code>stderr</code>（标准错误）</li>
</ul>
<p>借助 I/O 函数，我们始终可以和流一起工作。流是一个高级接口，可以代表一个设备或文件。从 C 的角度来看，我们在从文件读取和命令行读取没有任何差异：不论如何，它都是一个 I/O 流。</p>
<p>那是我们需要牢记的一件事情。</p>
<p>某些函数是为与特定的流一起工作而设计的，就像 <code>printf()</code>一样，我们用它来将字符串打印到 <code>stdout</code>。使用它更加通用的版本 <code>fprintf()</code> 时，我们可以指定我们要写到的流。</p>
<p>由于我最开始谈论的是 <code>printf()</code>，咱们现在就介绍它吧。</p>
<p><code>printf()</code> 是你在学习 C 编程时最先使用的函数之一。</p>
<p>在它最简单的使用形式中，你给它传递一个字符串字面量：</p>
<pre><code class="language-c">printf("hey!");
</code></pre>
<p>并且程序会将该字符串的内容打印到屏幕上。</p>
<p>你可以打印一个变量的值。但是这有点棘手，因为你需要添加一个特殊的字符，一个占位符，它会根据变量的类型变化。例如，我们为有符号十进制整数使用 <code>%d</code>：</p>
<pre><code class="language-c">int age = 37;

printf("My age is %d", age);
</code></pre>
<p>通过使用逗号，我现在可以打印多个变量：</p>
<pre><code class="language-c">int age_yesterday = 37;
int age_today = 36;

printf("Yesterday my age was %d and today is %d", age_yesterday, age_today);
</code></pre>
<p>还有其它像 <code>%d</code> 一样的格式指示符：</p>
<ul>
<li><code>%c</code> 用于字符</li>
<li><code>%s</code> 用于字符串</li>
<li><code>%f</code> 用于浮点数</li>
<li><code>%p</code> 用于指针</li>
</ul>
<p>还有很多。</p>
<p>我们可以在 <code>printf()</code> 中使用转义字符，比如 <code>\n</code> 可以用来让输出创建一个新行。</p>
<h3 id="scanf"><code>scanf()</code></h3>
<p><code>printf()</code> 被用作输出函数。我现在想介绍一个输入函数，这样我们就能完成所有的 I/O 操作：<code>scanf()</code>。</p>
<p>这个函数被用来从用户运行的程序，从命令行获取一个值。</p>
<p>我们必须先定义一个变量，它将被用来存放我们从输入中获取的值：</p>
<pre><code class="language-c">int age;
</code></pre>
<p>然后我们调用 <code>scanf()</code>，传入两个参数：变量的格式（类型），和变量的地址：</p>
<pre><code class="language-c">scanf("%d", &amp;age);
</code></pre>
<p>如果我们想在输入时获取一个字符串，还记得字符串名是一个指向第一个字符的指针，所以你不需要在它前面加上 <code>&amp;</code>：</p>
<pre><code class="language-c">char name[20];
scanf("%s", name);
</code></pre>
<p>这里是一个小程序，它同时使用了 <code>printf()</code> 和 <code>scanf()</code>：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  char name[20];
  printf("Enter your name: ");
  scanf("%s", name);
  printf("you entered %s", name);
}
</code></pre>
<h2 id="variable-scope">变量作用域</h2>
<p>当你在 C 程序中定义一个变量时，根据你声明它的位置，它会有一个不同的 <strong>作用域（scope）</strong>。</p>
<p>这意味着它将会在某些地方可用，而在其它地方不可用。</p>
<p>该位置决定了两种类型的变量：</p>
<ul>
<li><strong>全局变量（global variables）</strong></li>
<li><strong>局部变量（local variables）</strong></li>
</ul>
<p>这就是区别：在函数内部声明的变量就是局部变量，比如这个：</p>
<pre><code class="language-c">int main(void) {
  int age = 37;
}
</code></pre>
<p>局部变量只有在函数内才能访问，它们会在函数结束后不复存在。它们会被从内存中清除掉（有一些例外）。</p>
<p>定义在函数外部的变量就是全局变量，比如这个示例：</p>
<pre><code class="language-c">int age = 37;

int main(void) {
  /* ... */
}
</code></pre>
<p>全局变量可以从程序中的任何一个函数访问，它们在整个程序的执行过程中都是可用的，直到程序结束。</p>
<p>我提到过局部变量在函数结束之后就不再可用。</p>
<p>原因是局部变量默认是在 <strong>栈（stack）</strong> 上声明的，除非你使用指针在堆中显式地分配它们。但是这样一来，你就不得不自己管理内存了。</p>
<h2 id="static-variables">静态变量</h2>
<p>在函数内部，你可以使用 <code>static</code> 关键字初始化一个 <strong>静态变量（static variable）</strong>。</p>
<p>我说了“在函数内部”，因为全局变量默认就是静态的，所以没有必要再添加这个关键字。</p>
<p>什么是静态变量？静态变量在没有声明初始值的时候会被初始化为 0，并且它会在函数调用中保持该值。</p>
<p>考虑这个函数：</p>
<pre><code class="language-c">int incrementAge() {
  int age = 0;
  age++;
  return age;
}
</code></pre>
<p>如果我们调用一次 <code>incrementAge()</code>，我们将会得到返回值 <code>1</code>。如果我们再调用一次，我们总是会得到 1，因为 <code>age</code> 是一个局部变量并且在每次调用该函数的时候都会被重新初始化为 <code>0</code>。</p>
<p>如果我们将该函数改为：</p>
<pre><code class="language-c">int incrementAge() {
  static int age = 0;
  age++;
  return age;
}
</code></pre>
<p>现在我们每调用一次这个函数，我们就会得到一个增加了的值：</p>
<pre><code class="language-c">printf("%d\n", incrementAge());
printf("%d\n", incrementAge());
printf("%d\n", incrementAge());
</code></pre>
<p>将会给我们：</p>
<pre><code>1
2
3
</code></pre>
<p>我们也可以在 <code>static int age = 0;</code> 中省略初始化 <code>age</code> 为 0 的代码，只写 <code>static int age;</code>，因为静态变量在创建时会自动设置为 0。</p>
<p>我们也可以有静态数组。这时，每一个数组元素都被初始化为 0：</p>
<pre><code class="language-c">int incrementAge() {
  static int ages[3];
  ages[0]++;
  return ages[0];
}
</code></pre>
<h2 id="global-variables">全局变量</h2>
<p>在这一节中，我想多谈论一点 <strong>全局变量与局部变量</strong> 之间的差异。</p>
<p><strong>局部变量</strong> 被定义在函数内部，只在该函数内可用。</p>
<p>就像这样：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
  char j = 0;
  j += 10;
  printf("%u", j); //10
}
</code></pre>
<p><code>j</code> 在 <code>main</code> 函数之外的任何地方都不可用。</p>
<p><strong>全局变量</strong> 定义在所有函数的外部，就像这样：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

char i = 0;

int main(void) {
  i += 10;
  printf("%u", i); //10
}
</code></pre>
<p>全局变量可以被程序内的任何函数访问。该访问并不只局限于读取全局变量的值：任何函数都可以更新全局变量的值。</p>
<p>因此，全局变量是一种在函数间共享相同数据的一种方式。</p>
<p>局部变量的主要不同在于，分配给局部变量的内存会在函数结束之后立即释放。</p>
<p>全局变量只在程序结束时才会释放。</p>
<h2 id="type-definitions">类型定义</h2>
<p>C 中的 <code>typedef</code> 关键字允许你定义新的类型。</p>
<p>我们可以从 C 内置的类型开始创建自己的类型，使用这个语法：</p>
<pre><code class="language-c">typedef existingtype NEWTYPE
</code></pre>
<p>按照惯例，我们创建的新类型通常是大写的。</p>
<p>这样可以更加容易区分它，并且可以立即识别出它是一种类型。</p>
<p>例如，我们可以定义一个新的 <code>NUMBER</code> 类型，它还是 <code>int</code>：</p>
<pre><code class="language-c">typedef int NUMBER
</code></pre>
<p>一旦你这么做了之后，你就可以定义新的 <code>NUMBER</code> 变量了：</p>
<pre><code class="language-c">NUMBER one = 1;
</code></pre>
<p>现在你可能会问：为什么？为什么不直接使用内置的 <code>int</code> 类型呢？</p>
<p>嗯，当两个东西搭配在一起的时候，<code>typedef</code> 会变得真的很有用：枚举类型和结构体。</p>
<h2 id="enumerated-types">枚举类型</h2>
<p>使用 <code>typedef</code> 和 <code>enum</code> 关键字，我们可以定义具有指定值的类型。</p>
<p>这是 <code>typedef</code> 关键字最重要的使用场景之一。</p>
<p>这是枚举类型的语法：</p>
<pre><code class="language-c">typedef enum {
  //值……
}
</code></pre>
<p>按照惯例，我们创建的枚举类通常是大写的。</p>
<p>这里是一个简单的示例：</p>
<pre><code class="language-c">typedef enum {
  true,
  false
} BOOLEAN;
</code></pre>
<p>C 自带 <code>bool</code> 类型，所以这个示例并不实用，但是它会让你领悟到其中的精髓。</p>
<p>另一个示例是定义一周中的那几个日子：</p>
<pre><code class="language-c">typedef enum {
  monday,  
  tuesday,
  wednesday,
  thursday,
  friday,
  saturday,
  sunday
} WEEKDAY;
</code></pre>
<p>这里是使用这个枚举类的一个简单程序：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

typedef enum {
  monday,  
  tuesday,
  wednesday,
  thursday,
  friday,
  saturday,
  sunday
} WEEKDAY;

int main(void) {
  WEEKDAY day = monday;

  if (day == monday) {
    printf("It's monday!"); 
  } else {
    printf("It's not monday"); 
  }
}
</code></pre>
<p>枚举定义中的每个枚举项在内部都与一个整数配对。所以在这个示例中 <code>monday</code> 是 0，<code>tuesday</code> 是 1，以此类推。</p>
<p>这意味着对应的条件可以是 <code>if (day == 0)</code> 而不是 <code>if (day == monday)</code>，但是对于我们人类来说，使用名字比数字更合理，所以它是一个非常便利的语法。</p>
<h2 id="structures">结构体</h2>
<p>利用 <code>struct</code> 关键字，我们可以使用基本的 C 类型创建复杂的数据结构。</p>
<p>结构体是一组由不同类型的值组成的集合。C 中的数组被限制为一种类型，所以结构体在很多用例中会显得非常有趣。</p>
<p>这里是结构体的语法：</p>
<pre><code class="language-c">struct &lt;structname&gt; {
  //变量……
};
</code></pre>
<p>示例：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
};
</code></pre>
<p>通过将变量添加到右花括号之后，分号之前，你可以声明类型为该结构体的变量，就像这样：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
} flavio;
</code></pre>
<p>或者多个变量也行，就像这样：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
} flavio, people[20];
</code></pre>
<p>这次我声明一个名为 <code>flavio</code> 的 <code>person</code> 变量，以及一个具有 20 个 <code>person</code> 的名为 <code>people</code> 的数组。</p>
<p>我们也可以稍后再声明变量，使用这个语法：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
};

struct person flavio;
</code></pre>
<p>我们可以在声明的时候初始化一个结构体：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };
</code></pre>
<p>一旦定义了结构体，我们就可以使用一个点（<code>.</code>）来访问它里面的值了：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };
printf("%s, age %u", flavio.name, flavio.age);
</code></pre>
<p>我们也可以使用点语法改变结构体中的值：</p>
<pre><code class="language-c">struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };

flavio.age = 38;
</code></pre>
<p>结构体非常有用，因为它们既可以作为函数的参数，也可以作为函数的返回值，以及它们内部的嵌入变量。每个变量都有一个标签。</p>
<p>注意到结构体是 <strong>复制传递</strong> 的，这一点很重要，除非，当然你可以传递一个指向结构体的指针，这种情况下它就是引用传递。</p>
<p>使用 <code>typedef</code>，我们可以简化处理结构体时的代码。</p>
<p>咱们看一个示例：</p>
<pre><code class="language-c">typedef struct {
  int age;
  char *name;
} PERSON;
</code></pre>
<p>按照惯例，我们使用 <code>typedef</code> 创建的结构体通常是大写的。</p>
<p>现在，我们可以像这样声明一个新的 <code>PERSON</code> 变量：</p>
<pre><code class="language-c">PERSON flavio;
</code></pre>
<p>并且我们可以用这种方式在声明的时候初始化它们：</p>
<pre><code class="language-c">PERSON flavio = { 37, "Flavio" };
</code></pre>
<h2 id="command-line-parameters">命令行参数</h2>
<p>在 C 程序中，你可能需要在命令启动时从命令行接收参数。</p>
<p>对于简单的需求而言，你只需要将 <code>main()</code> 函数的签名从</p>
<pre><code class="language-c">int main(void)
</code></pre>
<p>修改为</p>
<pre><code class="language-c">int main (int argc, char *argv[])
</code></pre>
<p><code>argc</code> 是一个整数，包含从命令行提供的参数的数量。</p>
<p><code>argv</code> 是一个字符串数组。</p>
<p>当程序开始运行时，我们用这两个参数给主函数提供参数。</p>
<p>注意 <code>argv</code> 数组中总是至少有一个元素：程序的名字。</p>
<p>咱们以我们用来运行程序的 C 编译器作为示例吧，就像这样：</p>
<pre><code class="language-c">gcc hello.c -o hello
</code></pre>
<p>如果这就是我们的程序，我们的 <code>argc</code> 将会是 4，<code>argv</code> 将是一个包含以下内容的数组：</p>
<ul>
<li><code>gcc</code></li>
<li><code>hello.c</code></li>
<li><code>-o</code></li>
<li><code>hello</code></li>
</ul>
<p>咱们写一个打印它收到的参数的程序吧：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main (int argc, char *argv[]) {
  for (int i = 0; i &lt; argc; i++) {
    printf("%s\n", argv[i]);
  }
}
</code></pre>
<p>如果我们的程序名为 <code>hello</code>，并且我们像这样运行它：<code>./hello</code>，我们就会得到以下输出：</p>
<pre><code>./hello
</code></pre>
<p>如果我们传递一些随机参数，就像这样：<code>./hello a b c</code>，我们竟会在终端中得到这个输出：</p>
<pre><code>./hello
a
b
c
</code></pre>
<p>对于简单的需求而言，这个系统工作得很好。对于更加复杂的需求，有一些常用的包，比如 <strong>getopt</strong>。</p>
<h2 id="header-files">头文件</h2>
<p>简单的程序可以直接放在单个文件中。但是当你的程序变大，将它放在单个文件中就不可能了。</p>
<p>你可以将程序一些部分移动到一个单独的文件中，然后创建一个 <strong>头文件</strong>。</p>
<p>头文件看起来就像普通的 C 文件一样，但是它是以 <code>.h</code> 而不是 <code>.c</code> 结尾的。它里面的内容是 <strong>声明</strong>，而不是函数的实现和程序的其它部分。</p>
<p>你已经在第一次使用 <code>printf()</code> 函数或其它 I/O 函数的时候使用过头文件了，如果你要使用它，需要输入以下内容：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
</code></pre>
<p><code>#include</code> 是一个预处理器指令。</p>
<p>该预处理器会在标准库中寻找 <code>stdio.h</code> 文件，因为你使用了花括号包裹它。若要包含你自己的头文件，你需要使用引号（<code>"</code>），就像这样：</p>
<pre><code class="language-c">#include "myfile.h"
</code></pre>
<p>上述代码会让预处理器在当前文件夹内寻找 <code>myfile.h</code>。</p>
<p>你也可以使用文件夹结构的库：</p>
<pre><code class="language-c">#include "myfolder/myfile.h"
</code></pre>
<p>咱们看一个示例。这个程序计算自给定年份以来的年数：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int calculateAge(int year) {
  const int CURRENT_YEAR = 2020;
  return CURRENT_YEAR - year;
}

int main(void) {
  printf("%u", calculateAge(1983));
}
</code></pre>
<p>假设我想将 <code>caculateAge</code> 函数移到一个单独的文件中。</p>
<p>我创建一个名为 <code>calculate_age.c</code> 的文件：</p>
<pre><code class="language-c">int calculateAge(int year) {
  const int CURRENT_YEAR = 2020;
  return CURRENT_YEAR - year;
}
</code></pre>
<p>我还创建了一个名为 <code>calculate_age.h</code> 的文件，我在其中放入了 <em>函数原型</em>，除了函数体，它与 <code>.c</code> 文件中的函数完全相同：</p>
<pre><code class="language-c">int calculateAge(int year);
</code></pre>
<p>现在在主 <code>.c</code> 文件中，我们可以移除 <code>calculateAge()</code> 函数的定义，并且我们可以导入 <code>calculate_age.h</code>，它会让 <code>calculateAge()</code> 函数可用：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include "calculate_age.h"

int main(void) {
  printf("%u", calculateAge(1983));
}
</code></pre>
<p>别忘了编译多个文件组成的程序，你需要在命令行中列出它们，就像这样：</p>
<pre><code class="language-sh">gcc -o main main.c calculate_age.c
</code></pre>
<p>如果配置更加复杂，一个告诉编译器如何编译该程序的 Makefile 是必需的。</p>
<h2 id="the-preprocessor">预处理器</h2>
<p>预处理器是一个工具，当我们用 C 编程时，它对我们有很大的帮助。它是 C 标准的一部分，就像语言本身、编译器和标准库一样。</p>
<p>它解析我们的程序，确保编译器在处理之前获得所有需要的东西。</p>
<p>在实践中，它是做什么的呢？</p>
<p>例如，它查找你使用 <code>#include</code> 指令包含的所有头文件。</p>
<p>它还查看你使用 <code>#define</code> 定义的每个常量并将其替换为实际的值。</p>
<p>这只是一个开始。我提到了这两个操作，是因为它们是最常见的两个。预处理器能做的事情还有很多。</p>
<p>你有注意到 <code>#include</code> 和 <code>#define</code> 在开头有一个 <code>#</code> 吗？那在预处理器指令中是很常见的。如果某一行以 <code>#</code> 开始，它就会被预处理器关照。</p>
<h3 id="">条件</h3>
<p>我们能做的一件事情是使用条件让表达式决定程序的编译方式。</p>
<p>例如，我们可以检查 <code>DEBUG</code> 常量是否为 0：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

const int DEBUG = 0;

int main(void) {
#if DEBUG == 0
  printf("I am NOT debugging\n");
#else
  printf("I am debugging\n");
#endif
}
</code></pre>
<h3 id="">符号常量</h3>
<p>我们可以定义一个 <strong>符号常量（symbolic constant）</strong>：</p>
<pre><code class="language-c">#define VALUE 1
#define PI 3.14
#define NAME "Flavio"
</code></pre>
<p>当我们在自己的程序中使用 NAME 或 PI 或 VALUE 时，预处理器会在执行程序之前将名字替换成对应的值。</p>
<p>符号常量非常有用，因为我们可以给值名字，而不用在编译时创建变量。</p>
<h3 id="">宏</h3>
<p>我们还可以使用 <code>#define</code> 定义 <strong>宏（macro）</strong>。宏与符号常量之间的差别在于：宏可以接受一个参数，并且通常包含代码，而符号常量只是一个值：</p>
<pre><code class="language-c">#define POWER(x) ((x) * (x))
</code></pre>
<p>注意参数两侧的括号：当宏在预编译过程中被替换时，这是一个避免问题的好方法。</p>
<p>然后我们可以在代码中使用它，像这样：</p>
<pre><code class="language-c">printf("%u\n", POWER(4)); //16
</code></pre>
<p>它与函数之间的一个大差别就是：宏不会声明参数或返回值的类型，这在一些场景中可能很方便。</p>
<p>然而，宏的定义被限制成只有一行。</p>
<h3 id="ifdefined">If defined</h3>
<p>我们可以使用 <code>#ifdef</code> 来检查某个符号常量或宏是否被定义过：</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define VALUE 1

int main(void) {
#ifdef VALUE
  printf("Value is defined\n");
#else
  printf("Value is not defined\n");
#endif
}
</code></pre>
<p>我们也可以使用 <code>#ifndev</code> 检查对立面（宏未定义）。</p>
<p>我们还可以使用 <code>#if defined</code> 和 <code>#if !defined</code> 来达到同样的目的。</p>
<p>像这样将一些代码块包裹到单个块中是很常见的：</p>
<pre><code class="language-c">#if 0

#endif
</code></pre>
<p>这样可以临时防止程序运行，也可以使用一个 DEBUG 符号常量：</p>
<pre><code class="language-c">#define DEBUG 0

#if DEBUG
  // 当 DEBUG 不为 0 时，代码才会被发给编译器
#endif
</code></pre>
<h3 id="">你可以使用的预定义的符号常量</h3>
<p>预处理器还定义了很多你可以直接使用的符号常量，它们的名字的前后有两个下划线作为标识，包括：</p>
<ul>
<li><strong><code>__LINE__</code></strong> 代表源代码文件中的当前行</li>
<li><strong><code>__FILE__</code></strong> 代表文件的名字</li>
<li><strong><code>__DATE__</code></strong> 表示编译日期，格式为 <code>Mmm gg aaaa</code></li>
<li><strong><code>__TIME__</code></strong> 表示编译实践，格式为 <code>hh:mm:ss</code></li>
</ul>
<h2 id="conclusion">结语</h2>
<p>非常感谢阅读本手册！</p>
<p>我希望它将鼓励你去了解更多有关 C 的知识。</p>
<p>若想查看更多教程，你可以访问我的<a href="https://flaviocopes.com/">博客</a>。</p>
<p>通过 <a href="mailto:hey@flaviocopes.com">hey@flaviocopes.com</a> 给我发送反馈、勘误或者意见。</p>
<p>记住：<a href="https://flaviocopes.com/page/c-handbook/">你可以从这里获得这本手册的 PDF 或 ePub 版本</a>。</p>
<p>你可以在 Twitter <a href="https://twitter.com/flaviocopes">@flaviocopes</a> 联系我。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/the-c-beginners-handbook/">The C Beginner's Handbook: Learn C Programming Language basics in just a few hours</a>，作者：<a href="https://www.freecodecamp.org/news/author/flavio/">Flavio Copes</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 用 C 语言来实现一个简单的网络客户端 ]]>
                </title>
                <description>
                    <![CDATA[ 当输入网址敲下回车的那一刻浏览器为我们做了什么呢？不妨今天就来看看用 C 语言怎么做到下载一个 HTML 页面的。 当我们在浏览器输入网址按下回车，浏览器向服务器发送一些数据。 GET / HTTP/1.1 Host: localhost 譬如说上面这样的，服务器端的程序接收到了之后就会做出一些回应，有可能是 HTML 页面，或者说其他的数据。 那怎么用 C 语言来写个程序发送这个数据呢？在 Unix 里面一切都是文件，每个进程都有一张表来记录打开的文件，这张表记录了文件的指针和一个数字。 那其实向网络的另一端发送数据就是往某个文件里头写数据。我们用 C 语言往文件里头写数据读数据就行了咯。我们怎么知道要往什么地方写，从什么地方读呢？ getaddrinfo() 获取域名的地址 既然要向服务器发送数据，得需要知道服务器的地址。我们需要知道 IP 地址啦，端口，协议什么的。到了今天这些当然不需要手动来填了。我们有一个函数  getaddrinfo()  ，它会帮我们填好IP，端口那些参数。 #include <sys/types.h> #include <sys/so ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/c-simple-network-client/</link>
                <guid isPermaLink="false">5e9ac078db4be8080eb70989</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 浏览器 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 客户端 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 王调科 ]]>
                </dc:creator>
                <pubDate>Wed, 09 Sep 2020 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/04/0_ZoBttIsLXw2VVC4o.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>当输入网址敲下回车的那一刻浏览器为我们做了什么呢？不妨今天就来看看用 C 语言怎么做到下载一个 HTML 页面的。</p>
<p>当我们在浏览器输入网址按下回车，浏览器向服务器发送一些数据。</p>
<pre><code>GET / HTTP/1.1
Host: localhost
</code></pre>
<p>譬如说上面这样的，服务器端的程序接收到了之后就会做出一些回应，有可能是 HTML 页面，或者说其他的数据。</p>
<p>那怎么用 C 语言来写个程序发送这个数据呢？在 Unix 里面一切都是文件，每个进程都有一张表来记录打开的文件，这张表记录了文件的指针和一个数字。</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/1-2.png" alt="descriptor table from &quot;head first c" width="1542" height="802" loading="lazy"></p>
<p>那其实向网络的另一端发送数据就是往某个文件里头写数据。我们用 C 语言往文件里头写数据读数据就行了咯。我们怎么知道要往什么地方写，从什么地方读呢？</p>
<h2 id="getaddrinfo">getaddrinfo() 获取域名的地址</h2>
<p>既然要向服务器发送数据，得需要知道服务器的地址。我们需要知道 IP 地址啦，端口，协议什么的。到了今天这些当然不需要手动来填了。我们有一个函数 <code>getaddrinfo()</code> ，它会帮我们填好IP，端口那些参数。</p>
<pre><code>#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;

int
getaddrinfo(const char *hostname,           // e.g. "www.example.com" or IP
            const char *servname,           // e.g. "http" or port number
            const struct addrinfo *hints, 
            struct addrinfo **res);
</code></pre>
<p>后面两个参数一个叫做 hints，一个叫做 res。顾名思义，我们要给它一点提示，暗示它应该怎么去拿到地址信息。然后会把回应的 response 放到 res 里面去。</p>
<p>不妨先看一个 showip 的例子。这个程序根据域名，去查询这个域名对应的 IP 地址是多少，然后我们把 IP 地址输出。当然计算机里面的东西都是二进制的，我们需要调用一些函数来帮我们把数据转换成需要的样子。函数的用法都可以用 man 命令去查询。</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;
#include &lt;string.h&gt;
#include &lt;arpa/inet.h&gt;

int main(int argc, char const *argv[])
{
    struct addrinfo hints, *res;

    memset(&amp;hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    int status = getaddrinfo("www.example.com", "http", &amp;hints, &amp;res);

    if (status != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        return 1;
    }

    char ipstr[INET6_ADDRSTRLEN];
    for (struct addrinfo *p = res; p != NULL; p = p-&gt;ai_next) {
        void *addr = NULL;
        if (p-&gt;ai_family == AF_INET) {
            struct sockaddr_in *sa = (struct sockaddr_in *)p-&gt;ai_addr;
            addr = &amp;(sa-&gt;sin_addr);
        } else {
            struct sockaddr_in6 *sa = (struct sockaddr_in6 *)p-&gt;ai_addr;
            addr = &amp;(sa-&gt;sin6_addr);
        }
        inet_ntop(p-&gt;ai_family, addr, ipstr, INET6_ADDRSTRLEN);
        printf("%s\n", ipstr);
    }
    freeaddrinfo(res);
    return 0;
}

</code></pre>
<p>getaddrinfo() 会把一个包含地址信息的链表交给 res，res 是链表的头。然后我们可以写一个循环去遍历链表，输出得到的 IP 地址。我们的重点不是这个，重点是 getaddrinfo() 是个好帮手，能让我们不必再手动填结构的数据了。</p>
<h2 id="socket">socket() 获取文件描述符</h2>
<p>socket 中文叫做套接字，在 Unix/Linux 里头其实就是一个文件描述符（file descriptor)，而所谓的文件描述符就是一个数字。套接字是双向的数据流，你可以往里头写数据，也可以从这里读数据。</p>
<pre><code>#include &lt;sys/socket.h&gt;

int
socket(int domain, int type, int protocol);
</code></pre>
<p>我们用 getaddrinfo() 得到的 res 来填这三个参数就好了。譬如可能是这样的。</p>
<pre><code>int s;
struct addrinfo hints, *res;

getaddrinfo("www.example.com", "http", &amp;hints, &amp;res);

s = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);
</code></pre>
<p>socket() 返回的是一个 int，这个数字就是文件描述符。得到了文件描述符那就能对文件描述符所代表的数据流写数据了。</p>
<h2 id="connect">connect() 把套接字连接到远程端口</h2>
<pre><code>#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;

int
connect(int socket, const struct sockaddr *address, socklen_t address_len);
</code></pre>
<p>把套接字连接到远程端口我们就可以对套接字读数据写数据了。</p>
<pre><code>struct addrinfo hints, *res;
int sockfd;

// 首先用 getaddrinfo() 加载地址的结构

memset(&amp;hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "http", &amp;hints, &amp;res);

// 套接字

sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);

// 连接!

connect(sockfd, res-&gt;ai_addr, res-&gt;ai_addrlen);
</code></pre>
<p>connect() 第一个参数是 socket() 返回的那个文件描述符，然后后面的 address 和 address_len 用 res 里头的成员来填。</p>
<h2 id="sendrecv">send() 和 recv()</h2>
<p>有了 socket 就要用 send() 和 recv() 来发送数据和接收数据了。socket 是一个文件描述符，既然是一个文件描述符那为什么不直接用 read() 和 write() 呢？当然可以用，但是 send() 和 recv() 能更好的控制数据传输。</p>
<pre><code>#include &lt;sys/socket.h&gt;

ssize_t
send(int socket, const void *buffer, size_t length, int flags);

ssize_t
recv(int socket, void *buffer, size_t length, int flags);
</code></pre>
<p>连上网络服务器后至少需要发送三样东西</p>
<pre><code>GET / HTTP/1.0
Host: www.example.com

</code></pre>
<p>最后有个空行。那来看看最终的程序的样子吧。</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;

int main(int argc, char const *argv[])
{
    struct addrinfo hints, *res;
    int sockfd, recvd;

    memset(&amp;hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    getaddrinfo("www.example.org", "http", &amp;hints, &amp;res);

    sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);
    connect(sockfd, (struct sockaddr *)res-&gt;ai_addr, res-&gt;ai_addrlen);

    send(sockfd, "GET / HTTP/1.0\r\n"
                 "Host: www.example.com\r\n\r\n", 47, 0);

    char buffer[1024];
    while ((recvd = recv(sockfd, buffer, sizeof(buffer) -1, 0)) != 0) {
        buffer[recvd] = '\0';
        printf("%s", buffer);
    }
    freeaddrinfo(res);
    close(sockfd);
    return 0;
}
</code></pre>
<p>send() 函数的第二个参数就是要发送的数据，每行以 <code>\r\n</code> 结尾，意思就是回车换行。recv() 函数可能不能一次性接收完所有的数据，所以我们应该写个循环来不断的从网络上接收数据。recv() 函数会返回接收了多少个字节的数据，在接收的数据后面加个 <code>\0</code> 让它成为一个 C 语言的字符串，然后用 printf() 输出。</p>
<p>必须记得要检查是否发生错误，这里没处理只是想让这个程序看起来不那么可怕。下面是处理了错误的版本。</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;
#include &lt;string.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;

int main(int argc, char const *argv[])
{
    struct addrinfo hints, *p, *res;
    int sockfd, recvd, status;

    memset(&amp;hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo("www.example.org", "http", &amp;hints, &amp;res)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    for (p = res; p != NULL; p = p-&gt;ai_next) {
        sockfd = socket(p-&gt;ai_family, p-&gt;ai_socktype, p-&gt;ai_protocol);
        if (sockfd == -1) {
            perror("socket");
            continue;
        }
        if (connect(sockfd, (struct sockaddr *)p-&gt;ai_addr, p-&gt;ai_addrlen) == -1) {
            perror("connect failed. retrying...");
            continue;
        }
        break;
    }
    freeaddrinfo(res);

    if (p == NULL) {
        fprintf(stderr, "failed!");
        exit(1);
    }

    if (send(sockfd, "GET / HTTP/1.0\r\n"
                 "Host: www.example.com\r\n\r\n", 47, 0) == -1) {
        perror("send");
        exit(1);
    }

    char buffer[1024];
    while ((recvd = recv(sockfd, buffer, sizeof(buffer) -1, 0)) != 0) {
        buffer[recvd] = '\0';
        printf("%s", buffer);
    }
    close(sockfd);
    return 0;
}

</code></pre>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
