初学者c语言编程软件_C初学者手册:仅需几个小时即可学习C编程语言基础知识

初学者c语言编程软件

This C Beginner's Handbook follows the 80/20 rule. You'll learn 80% of the C programming language in 20% of the time.

此C初学者手册遵循80/20规则。 您将在20%的时间里学习80%的C编程语言。

This approach will give you a well-rounded overview of the language.

这种方法将为您提供全面的语言概述。

This handbook does not try to cover everything under the sun related to C. It focuses on the core of the language, trying to simplify the more complex topics.

本手册并不试图涵盖与C有关的所有阳光。它着重于语言的核心,试图简化更复杂的主题。

And note: You can get a PDF and ePub version of this C Beginner's Handbook here.

请注意: 您可以在此处获得本C初学者手册的PDF和ePub版本 。

Enjoy!

请享用!

目录 (Table of Contents)

  1. Introduction to C

    C入门

  2. Variables and types

    变量和类型

  3. Constants

    常数

  4. Operators

    经营者

  5. Conditionals

    有条件的

  6. Loops

    循环

  7. Arrays

    数组

  8. Strings

    弦乐

  9. Pointers

    指针

  10. Functions

    功能

  11. Input and output

    输入输出

  12. Variables scope

    变量范围

  13. Static variables

    静态变量

  14. Global variables

    全局变量

  15. Type definitions

    类型定义

  16. Enumerated Types

    枚举类型

  17. Structures

    结构体

  18. Command line parameters

    命令行参数

  19. Header files

    头文件

  20. The preprocessor

    预处理器

  21. Conclusion

    结论

C入门 (Introduction to C)

C is probably the most widely known programming language. It is used as the reference language for computer science courses all over the world, and it's probably the language that people learn the most in school along with Python and Java.

C可能是最广为人知的编程语言。 它被用作全世界计算机科学课程的参考语言,它可能是人们在学校中与Python和Java一起学习最多的语言。

I remember it being my second programming language ever, after Pascal.

我记得它是继Pascal之后的第二种编程语言。

C is not just what students use to learn programming. It's not an academic language. And I would say it's not the easiest language, because C is a rather low level programming language.

C不仅仅是学生用来学习编程的工具。 这不是一门学术语言。 我会说这不是最简单的语言,因为C是一种相当底层的编程语言。

Today, C is widely used in embedded devices, and it powers most of the Internet servers, which are built using Linux. The Linux kernel is built using C, and this also means that C powers the core of all Android devices. We can say that C code runs a good portion of the entire world. Right now. Pretty remarkable.

如今,C被广泛用于嵌入式设备中,并为大多数使用Linux构建的Internet服务器提供支持。 Linux内核是使用C构建的,这也意味着C是所有Android设备的核心。 可以说C代码在整个世界中占有很大的份额。 马上。 相当了不起。

When it was created, C was considered a high level language, because it was portable across machines. Today we kind of take for granted that we can run a program written on a Mac on Windows or Linux, perhaps using Node.js or Python.

创建C时,它被认为是高级语言,因为它可跨机器移植。 今天,我们理所当然地认为我们可以使用Node.js或Python在Windows或Linux上的Mac上运行编写的程序。

Once upon a time, this was not the case at all. What C brought to the table was a language that was simple to implement and that had a compiler that could be easily ported to different machines.

曾几何时,事实并非如此。 C带来的是一种易于实现的语言,并且具有可以轻松移植到不同机器的编译器。

I said compiler: C is a compiled programming language, like Go, Java, Swift or Rust. Other popular programming language like Python, Ruby or JavaScript are interpreted. The difference is consistent: a compiled language generates a binary file that can be directly executed and distributed.

我说过编译器:C是一种编译的编程语言,例如Go,Java,Swift或Rust。 其他流行的编程语言(如Python,Ruby或JavaScript)也可以进行解释。 区别是一致的:编译语言生成可以直接执行和分发的二进制文件。

C is not garbage collected. This means we have to manage memory ourselves. It's a complex task and one that requires a lot of attention to prevent bugs, but it is also what makes C ideal to write programs for embedded devices like Arduino.

C不是垃圾收集。 这意味着我们必须自己管理内存。 这是一项复杂的任务,需要大量关注以防止错误,但这也是C语言为嵌入式设备(如Arduino)编写程序的理想之选。

C does not hide the complexity and the capabilities of the machine underneath. You have a lot of power, once you know what you can do.

C不会隐藏下面机器的复杂性和功能。 一旦您知道自己能做什么,就拥有很大的力量。

I want to introduce the first C program now, which we'll call "Hello, World!"

我现在想介绍第一个C程序,我们将其称为“ Hello,World!”。

hello.c

你好ç

#include 

int main(void) {
    printf("Hello, World!");
}

Let's describe the program source code: we first import the stdio library (the name stands for standard input-output library).

让我们描述程序的源代码:我们首先导入stdio库(名称代表标准输入输出库)。

This library gives us access to input/output functions.

该库使我们可以访问输入/输出功能。

C is a very small language at its core, and anything that's not part of the core is provided by libraries. Some of those libraries are built by normal programmers, and made available for others to use. Some other libraries are built into the compiler. Like stdio and others.

C是一种非常小的语言,其核心是任何不属于核心的语言。 其中一些库是由普通程序员构建的,并可供其他人使用。 编译器还内置了其他一些库。 像stdio和其他人一样。

stdio is the library that provides the printf() function.

stdio是提供printf()函数的库。

This function is wrapped into a main() function. The main() function is the entry point of any C program.

该函数包装在main()函数中。 main()函数是任何C程序的入口点。

But what is a function, anyway?

但是,什么是函数?

A function is a routine that takes one or more arguments, and returns a single value.

函数是一个接受一个或多个参数并返回单个值的例程。

In the case of main(), the function gets no arguments, and returns an integer. We identify that using the void keyword for the argument, and the int keyword for the return value.

对于main() ,该函数不获取任何参数,并返回一个整数。 我们确定使用void关键字作为参数,使用int关键字作为返回值。

The function has a body, which is wrapped in curly braces. Inside the body we have all the code that the function needs to perform its operations.

该函数具有一个主体,该主体被大括号括起来。 在主体内部,我们具有函数执行其操作所需的所有代码。

The printf() function is written differently, as you can see. It has no return value defined, and we pass a string, wrapped in double quotes. We didn't specify the type of the argument.

如您所见, printf()函数的编写方式有所不同。 它没有定义返回值,我们传递了一个用双引号引起来的字符串。 我们没有指定参数的类型。

That's because this is a function invocation. Somewhere, inside the stdio library, printf is defined as

那是因为这是一个函数调用。 在stdio库中的某处, printf定义为

int printf(const char *format, ...);

You don't need to understand what this means now, but in short, this is the definition. And when we call printf("Hello, World!");, that's where the function is run.

您现在不需要了解这意味着什么,但是简而言之,这就是定义。 当我们调用printf("Hello, World!"); ,那就是函数运行的地方。

The main() function we defined above:

我们上面定义的main()函数:

#include 

int main(void) {
    printf("Hello, World!");
}

will be run by the operating system when the program is executed.

该程序执行时将由操作系统运行。

How do we execute a C program?

我们如何执行C程序?

As mentioned, C is a compiled language. To run the program we must first compile it. Any Linux or macOS computer already comes with a C compiler built-in. For Windows, you can use the Windows Subsystem for Linux (WSL).

如前所述,C是一种编译语言。 要运行该程序,我们必须首先对其进行编译。 任何Linux或macOS计算机都已经内置了C编译器。 对于Windows,可以使用Windows Linux子系统(WSL)。

In any case, when you open the terminal window you can type gcc, and this command should return an error saying that you didn't specify any file:

无论如何,当您打开终端窗口时,您可以键入gcc ,并且此命令应返回一条错误消息,指出您未指定任何文件:

That's good. It means the C compiler is there, and we can start using it.

非常好。 这意味着C编译器在那里,我们可以开始使用它了。

Now type the program above into a hello.c file. You can use any editor, but for the sake of simplicity I'm going to use the nano editor in the command line:

现在,将上面的程序键入hello.c文件。 您可以使用任何编辑器,但是为了简单起见,我将在命令行中使用nano编辑器:

Type the program:

输入程序:

Now press ctrl-X to exit:

现在按ctrl-X退出:

Confirm by pressing the y key, then press enter to confirm the file name:

y键确认,然后按Enter确认文件名:

That's it, we should be back to the terminal now:

就是这样,我们现在应该回到终端:

Now type

现在输入

gcc hello.c -o hello

The program should give you no errors:

该程序应该不会给您任何错误:

but it should have generated a hello executable. Now type

但是它应该已经生成了一个hello可执行文件。 现在输入

./hello

to run it:

运行它:

I prepend ./ to the program name to tell the terminal that the command is in the current folder

我在程序名称前添加./ ,以告知终端命令位于当前文件夹中

Awesome!

太棒了!

Now if you call ls -al hello, you can see that the program is only 12KB in size:

现在,如果调用ls -al hello ,则可以看到该程序的大小只有12KB:

This is one of the pros of C: it's highly optimized, and this is also one of the reasons it's this good for embedded devices that have a very limited amount of resources.

这是C语言的优点之一:高度优化,这也是对资源非常有限的嵌入式设备如此之好的原因之一。

变量和类型 (Variables and types)

C is a statically typed language.

C是一种静态类型的语言。

This means that any variable has an associated type, and this type is known at compilation time.

这意味着任何变量都具有关联的类型,并且该类型在编译时是已知的。

This is very different than how you work with variables in Python, JavaScript, PHP and other interpreted languages.

这与处理Python,JavaScript,PHP和其他解释语言中的变量的方式非常不同。

When you create a variable in C, you have to specify the type of a variable at the declaration.

在C语言中创建变量时,必须在声明中指定变量的类型。

In this example we initialize a variable age with type int:

在此示例中,我们使用int类型初始化变量age

int age;

A variable name can contain any uppercase or lowercase letter, can contain digits and the underscore character, but it can't start with a  digit. AGE and Age10 are valid variable names, 1age is not.

变量名可以包含任何大写或小写字母,可以包含数字和下划线字符,但不能以数字开头。 AGEAge10是有效的变量名,不是1age

You can also initialize a variable at declaration, specifying the initial value:

您还可以在声明时初始化变量,并指定初始值:

int age = 37;

Once you declare a variable, you are then able to use it in your program code. You can change its value at any time, using the = operator for example, like in age = 100; (provided the new value is of the same type).

声明变量后,便可以在程序代码中使用它。 您可以随时使用=运算符来更改其值,例如age = 100; (如果新值是相同类型的)。

In this case:

在这种情况下:

#include 

int main(void) {
    int age = 0;
    age = 37.2;
    printf("%u", age);
}

the compiler will raise a warning at compile time, and will convert the decimal number to an integer value.

编译器将在编译时发出警告,并将十进制数转换为整数值。

The C built-in data types are int, char, short, long, float, double, long double. Let's find out more about those.

C内置数据类型为intcharshortlongfloatdoublelong double 。 让我们进一步了解这些。

整数 (Integer numbers)

C provides us the following types to define integer values:

C为我们提供了以下类型来定义整数值:

  • char

    char

  • int

    int

  • short

    short

  • long

    long

Most of the time, you'll likely use an int to store an integer. But in some cases, you might want to choose one of the other 3 options.

大多数时候,您可能会使用int来存储整数。 但是在某些情况下,您可能要选择其他三个选项之一。

The char type is commonly used to store letters of the ASCII chart, but it can be used to hold small integers from -128 to 127. It takes at least 1 byte.

char类型通常用于存储ASCII图表的字母,但可以用于保存-128127小整数。 至少需要1个字节。

int takes at least 2 bytes. short takes at least 2 bytes. long takes at least 4 bytes.

int至少占用2个字节。 short至少需要2个字节。 long至少需要4个字节。

As you can see, we are not guaranteed the same values for different environments. We only have an indication. The problem is that the exact numbers that can be stored in each data type depends on the implementation and the architecture.

如您所见,我们不能保证在不同环境下使用相同的值。 我们只有一个指示。 问题在于,每种数据类型中可以存储的确切数字取决于实现和体系结构。

We're guaranteed that short is not longer than int. And we're guaranteed long is not shorter than int.

我们保证short不长于int 。 并且我们保证long不小于int

The ANSI C spec standard determines the minimum values of each type, and thanks to it we can at least know what's the minimum value we can expect to have at our disposal.

ANSI C规范标准确定每种类型的最小值,并且由于它,我们至少可以知道我们可以期望的最小值是多少。

If you are programming C on an Arduino, different board will have different limits.

如果您在Arduino上编写C语言,则不同的开发板会有不同的限制。

On an Arduino Uno board, int stores a 2 byte value, ranging from -32,768 to 32,767. On a Arduino MKR 1010, int stores a 4 bytes value, ranging from -2,147,483,648 to 2,147,483,647. Quite a big difference.

在Arduino Uno板上, int存储2字节值,范围-32,76832,767 。 在Arduino MKR 1010上, int存储4个字节的值,范围为-2,147,483,6482,147,483,647 。 差异很大。

On all Arduino boards, short stores a 2 bytes value, ranging from -32,768 to 32,767. long store 4 bytes, ranging from -2,147,483,648 to 2,147,483,647.

在所有Arduino板上, short存储一个2字节的值,范围为-32,76832,767long存储4个字节,范围从-2,147,483,6482,147,483,647

无符号整数 (Unsigned integers)

For all the above data types, we can prepend unsigned to start the range at 0, instead of a negative number. This might make sense in many cases.

对于上述所有数据类型,我们可以在unsigned添加范围以0开头,而不是负数。 在许多情况下,这可能是有道理的。

  • unsigned char will range from 0 to at least 255

    unsigned char范围是0到至少255

  • unsigned int will range from 0 to at least 65,535

    unsigned int范围是0到至少65,535

  • unsigned short will range from 0 to at least 65,535

    unsigned short范围从0到至少65,535

  • unsigned long will range from 0 to at least 4,294,967,295

    unsigned long范围从0到至少4,294,967,295

溢出的问题 (The problem with overflow)

Given all those limits, a question might come up: how can we make sure our numbers do not exceed the limit? And what happens if we do exceed the limit?

考虑到所有这些限制,可能会出现一个问题:我们如何确保我们的人数不超过限制? 如果我们超过了限制,会发生什么?

If you have an unsigned int number at 255, and you increment it, you'll get 256 in return. As expected. If you have an unsigned char number at 255, and you increment it, you'll get 0 in return. It resets starting from the initial possible value.

如果您在255处有一个unsigned int数,并对其进行递增,则将得到256。 如预期的那样。 如果您在255处有一个unsigned char数,并且对其进行递增,则将得到0。 从可能的初始值开始复位。

If you have a unsigned char number at 255 and you add 10 to it, you'll get the number 9:

如果您在255处有一个unsigned char数,并将其加10,则将得到数字9

#include 

int main(void) {
  unsigned char j = 255;
  j = j + 10;
  printf("%u", j); /* 9 */
}

If you don't have a signed value, the behavior is undefined. It will basically give you a huge number which can vary, like in this case:

如果您没有带符号的值,则行为是不确定的。 基本上,它将为您提供很大的数量,并且可能会有所不同,例如在这种情况下:

#include 

int main(void) {
  char j = 127;
  j = j + 10;
  printf("%u", j); /* 4294967177 */
}

In other words, C does not protect you from going over the limits of a type. You need to take care of this yourself.

换句话说,C不能保护您避免超出类型的限制。 您需要自己照顾。

声明错误类型时的警告 (Warnings when declaring the wrong type)

When you declare the variable and initialize it with the wrong value, the gcc compiler (the one you're probably using) should warn you:

当您声明变量并使用错误的值对其进行初始化时, gcc编译器(您可能正在使用的编译器)会警告您:

#include 

int main(void) {
  char j = 1000;
}
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.

And it also warns you in direct assignments:

它还会在直接任务中警告您:

#include 

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

But not if you increase the number using, for example, +=:

但是,如果使用+=来增加数字,则不会:

#include 

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

浮点数字 (Floating point numbers)

Floating point types can represent a much larger set of values than integers can, and can also represent fractions, something that integers can't do.

浮点类型可以表示比整数更大的一组值,并且还可以表示分数,而整数则不能。

Using floating point numbers, we represent numbers as decimal numbers times powers of 10.

使用浮点数,我们将数字表示为十进制数乘以10的幂。

You might see floating point numbers written as

您可能会看到浮点数写为

  • 1.29e-3

    1.29e-3

  • -2.3e+5

    -2.3e+5

and in other seemingly weird ways.

以及其他看似奇怪的方式。

The following types:

以下类型:

  • float

    float

  • double

    double

  • long double

    long double

are used to represent numbers with decimal points (floating point types). All can represent both positive and negative numbers.

用于表示带小数点的数字(浮点类型)。 全部都可以代表正数和负数。

The minimum requirements for any C implementation is that float can represent a range between 10^-37 and 10^+37, and is typically implemented using 32 bits. double can represent a bigger set of numbers. long double can hold even more numbers.

任何C实现的最低要求是float可以表示10 ^ -37到10 ^ + 37之间的范围,并且通常使用32位实现。 double可以代表更大的一组数字。 long double可以容纳更多数字。

The exact figures, as with integer values, depend on the implementation.

与整数值一样,确切的数字取决于实现方式。

On a modern Mac, a float is represented in 32 bits, and has a precision of 24 significant bits. 8 bits are used to encode the exponent.

在现代Mac上, float以32位表示,精度为24个有效位。 8位用于编码指数。

A double number is represented in 64 bits, with a precision of 53 significant bits. 11 bits are used to encode the exponent.

double精度数以64位表示,精度为53个有效位。 11位用于编码指数。

The type long double is represented in 80 bits, has a precision of 64 significant bits. 15 bits are used to encode the exponent.

long double类型用80位表示,精度为64个有效位。 15位用于编码指数。

On your specific computer, how can you determine the specific size of the types? You can write a program to do that:

在您的特定计算机上,如何确定类型的特定大小? 您可以编写一个程序来做到这一点:

#include 

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));
}

In my system, a modern Mac, it prints:

在我的系统中,现代Mac可以打印:

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

常数 (Constants)

Let's now talk about constants.

现在让我们谈谈常量。

A constant is declared similarly to variables, except it is prepended with the const keyword, and you always need to specify a value.

常量的声明与变量的声明类似,不同之处在于const带有const关键字,并且您始终需要指定一个值。

Like this:

像这样:

const int age = 37;

This is perfectly valid C, although it is common to declare constants uppercase, like this:

这是完全有效的C语言,尽管通常将常量声明为大写,如下所示:

const int AGE = 37;

It's just a convention, but one that can greatly help you while reading or writing a C program as it improves readability. Uppercase name means constant, lowercase name means variable.

这只是一个约定,但是在提高C语言的可读性的同时,可以极大地帮助您。 大写名称表示常量,小写名称表示变量。

A constant name follows the same rules for variable names: can contain any uppercase or lowercase letter, can contain digits and the underscore character, but it can't start with a digit. AGE and Age10 are valid variable names, 1AGE is not.

常量名称遵循与变量名称相同的规则:可以包含任何大写或小写字母,可以包含数字和下划线字符,但不能以数字开头。 AGEAge10是有效的变量名, 1AGE不是。

Another way to define constants is by using this syntax:

定义常量的另一种方法是使用以下语法:

#define AGE 37

In this case, you don't need to add a type, and you don't also need the = equal sign, and you omit the semicolon at the end.

在这种情况下,您不需要添加类型,也不需要=等号,并且最后省略了分号。

The C compiler will infer the type from the value specified, at compile time.

C编译器将在编译时根据指定的值推断类型。

经营者 (Operators)

C offers us a wide variety of operators that we can use to operate on data.

C为我们提供了多种运算符,可用于对数据进行运算。

In particular, we can identify various groups of operators:

特别是,我们可以识别出不同类型的运营商:

  • arithmetic operators

    算术运算符
  • comparison operators

    比较运算符
  • logical operators

    逻辑运算符
  • compound assignment operators

    复合赋值运算符
  • bitwise operators

    按位运算符
  • pointer operators

    指针运算符
  • structure operators

    结构算子
  • miscellaneous operators

    杂项运算符

In this section I'm going to detail all of them, using 2 imaginary variables a and b as examples.

在本节中,我将使用2个虚拟变量ab作为示例,详细介绍所有这些变量。

I am keeping bitwise operators, structure operators and pointer operators out of this list, to keep things simpler

我将按位运算符,结构运算符和指针运算符排除在此列表之外,以使事情更简单

算术运算符 (Arithmetic operators)

In this macro group I am going to separate binary operators and unary operators.

在这个宏组中,我将分开二进制运算符和一元运算符。

Binary operators work using two operands:

二进制运算符使用两个操作数进行工作:

Operator Name Example
= Assignment a = b
+ Addition a + b
- Subtraction a - b
* Multiplication a * b
/ Division a / b
% Modulo a % b
操作员 名称
= 分配 a = b
+ 加成 a + b
- 减法 a - b
* 乘法 a * b
/ a / b
% 模数 a % b

Unary operators only take one operand:

一元运算符只能使用一个操作数:

Operator Name Example
+ Unary plus +a
- Unary minus -a
++ Increment a++ or ++a
-- Decrement a-- or --a
操作员 名称
+ 一元加 +a
- 一元减 -a
++ 增量 a++++a
-- 减量 a----a

The difference between a++ and ++a is that a++ increments the a variable after using it. ++a increments the a variable before using it.

之间的差a++++aa++递增a使用后的变量。 ++a增量a使用它之前的变量。

For example:

例如:

int a = 2;
int b;
b = a++ /* b is 2, a is 3 */
b = ++a /* b is 4, a is 4 */

The same applies to the decrement operator.

递减运算符也是如此。

比较运算符 (Comparison operators)

Operator Name Example
== Equal operator a == b
!= Not equal operator a != b
> Bigger than a > b
< Less than a < b
>= Bigger than or equal to a >= b
<= Less than or equal to a <= b
操作员 名称
== 平等算子 a == b
!= 不等于运算符 a != b
> 大于 a > b
< 少于 a < b
>= 大于或等于 a >= b
<= 小于或等于 a <= b

逻辑运算符 (Logical operators)

  • ! NOT (example: !a)

    ! 不(例如: !a )

  • && AND (example: a && b)

    && AND(例如: a && b )

  • || OR (example: a || b)

    || 或(例如: a || b )

Those operators are great when working with boolean values.

这些运算符在使用布尔值时非常有用。

复合赋值运算符 (Compound assignment operators)

Those operators are useful to perform an assignment and at the same time perform an arithmetic operation:

这些运算符对于执行赋值和同时执行算术运算非常有用:

Operator Name Example
+= Addition assignment a += b
-= Subtraction assignment a -= b
*= Multiplication assignment a *= b
/= Division assignment a /= b
%= Modulo assignment a %= b
操作员 名称
+= 加法分配 a += b
-= 减法分配 a -= b
*= 乘法分配 a *= b
/= 部门分配 a /= b
%= 模分配 a %= b

三元运算符 (The ternary operator)

The ternary operator is the only operator in C that works with 3 operands, and it’s a short way to express conditionals.

三元运算符是C中唯一可以使用3个操作数的运算符,这是表达条件的一种简便方法。

This is how it looks:

看起来是这样的:

 ?  : 

Example:

例:

a ? b : c

If a is evaluated to true, then the b statement is executed, otherwise c is.

如果将a评估为true ,则执行b语句,否则执行c

The ternary operator is functionality-wise same as an if/else conditional, except it is shorter to express and it can be inlined into an expression.

三元运算符在功能上与if / else条件相同,只不过它的表示时间较短,可以内联到表达式中。

大小 (sizeof)

The sizeof operator returns the size of the operand you pass. You can pass a variable, or even a type.

sizeof运算符返回您传递的操作数的大小。 您可以传递变量,甚至可以传递类型。

Example usage:

用法示例:

#include 

int main(void) {
  int age = 37;
  printf("%ld\n", sizeof(age));
  printf("%ld", sizeof(int));
}

运算符优先级 (Operator precedence)

With all those operators (and more, which I haven't covered in this post, including bitwise, structure operators, and pointer operators), we must pay attention when using them together in a single expression.

对于所有这些运算符(以及我在本文中未介绍的更多运算符,包括按位运算符,结构运算符和指针运算符),在单个表达式中一起使用它们时,必须注意。

Suppose we have this operation:

假设我们执行以下操作:

int a = 2;
int b = 4;
int c = b + a * a / b - a;

What's the value of c? Do we get the addition being executed before the multiplication and the division?

c的值是多少? 我们是否在加法和除法之前执行加法?

There is a set of rules that help us solve this puzzle.

有一套规则可以帮助我们解决这个难题。

In order from less precedence to more precedence, we have:

为了从低优先级到高优先级,我们有:

  • the = assignment operator

    =赋值运算符

  • the + and - binary operators

    +- 二进制运算符

  • the * and / operators

    */运算符

  • the + and - unary operators

    +-一元运算符

Operators also have an associativity rule, which is always left to right except for the unary operators and the assignment.

运算符还具有关联规则,除一元运算符和赋值外,该规则始终从左到右。

In:

在:

int c = b + a * a / b - a;

We first execute a * a / b, which, due to being left-to-right, we can separate into a * a and the result / b: 2 * 2 = 4, 4 / 4 = 1.

我们首先执行a * a / b ,由于从左到右,我们可以将其分成a * a和结果/ b2 * 2 = 4 4 / 4 = 1

Then we can perform the sum and the subtraction: 4 + 1 - 2. The value of c is 3.

然后,我们可以执行的总和和减法:4 + 1 - 2的值c3

In all cases, however, I want to make sure you realize you can use parentheses to make any similar expression easier to read and comprehend.

但是,在所有情况下,我都想确保您意识到可以使用括号使任何相似的表达式更易于阅读和理解。

Parentheses have higher priority over anything else.

括号优先于其他任何内容。

The above example expression can be rewritten as:

上面的示例表达式可以重写为:

int c = b + ((a * a) / b) - a;

and we don't have to think about it that much.

而且我们不必考虑太多。

有条件的 (Conditionals)

Any programming language provides the programmers the ability to perform choices.

任何编程语言都可以为程序员提供执行选择的能力。

We want to do X in some cases, and Y in other cases.

在某些情况下,我们想做X,在其他情况下,我们要做Y。

We want to check data, and make choices based on the state of that data.

我们要检查数据,并根据该数据的状态进行选择。

C provides us 2 ways to do so.

C为我们提供了两种方法。

The first is the if statement, with its else helper, and the second is the switch statement.

第一个是if语句,带有else助手,第二个是switch语句。

如果 (if)

In an if statement, you can check for a condition to be true, and then execute the block provided in the curly brackets:

if语句中,可以检查条件是否为true,然后执行大括号中提供的代码块:

int a = 1;

if (a == 1) {
  /* do something */
}

You can append an else block to execute a different block if the original condition turns out to be false:

如果原始条件为假,则可以附加else块以执行else块:

int a = 1;

if (a == 2) {
  /* do something */
} else {
  /* do something else */
}

Beware of one common source of bugs - always use the comparison operator == in comparisons, and not the assignment operator =. If you don't, the if conditional check will always be true, unless the argument is 0, for example if you do:

注意一个常见的错误来源-在比较中始终使用比较运算符== ,而不要使用赋值运算符= 。 如果不这样做,则if条件检查将始终为true,除非参数为0 ,例如,如果这样做:

int a = 0;

if (a = 0) {
  /* never invoked */
}

Why does this happen? Because the conditional check will look for a boolean result (the result of a comparison), and the 0 number always equates to a false value. Everything else is true, including negative numbers.

为什么会这样? 因为条件检查将寻找布尔结果(比较的结果),所以0始终等于假值。 其他所有情况都是正确的,包括负数。

You can have multiple else blocks by stacking together multiple if statements:

通过将多个if语句堆叠在一起,可以拥有多个else块:

int a = 1;

if (a == 2) {
  /* do something */
} else if (a == 1) {
  /* do something else */
} else {
  /* do something else again */
}

开关 (switch)

If you need to do too many if / else / if blocks to perform a check, perhaps because you need to check the exact value of a variable, then switch can be very useful to you.

如果您需要执行过多的if / else / if块来执行检查,也许是因为您需要检查变量的确切值,那么switch对您非常有用。

You can provide a variable as condition, and a series of case entry points for each value you expect:

您可以提供一个变量作为条件,并为每个期望的值提供一系列case入口点:

int a = 1;

switch (a) {
  case 0:
    /* do something */
    break;
  case 1:
    /* do something else */
    break;
  case 2:
    /* do something else */
    break;
}

We need a break keyword at the end of each case to avoid the next case being executed when the one before ends. This "cascade" effect can be useful in some creative ways.

在每种情况下,我们都需要一个break关键字,以避免在前一种情况结束时执行下一种情况。 这种“级联”效果在某些创造性方面可能很有用。

You can add a "catch-all" case at the end, labeled default:

您可以在末尾添加一个“包罗万象”的案例,标记为default

int a = 1;

switch (a) {
  case 0:
    /* do something */
    break;
  case 1:
    /* do something else */
    break;
  case 2:
    /* do something else */
    break;
  default:
    /* handle all the other cases */
    break;
}

循环 (Loops)

C offers us three ways to perform a loop: for loops, while loops and do while loops. They all allow you to iterate over arrays, but with a few differences. Let's see them in detail.

C为我们提供了三种执行循环的方法: for循环while循环do while循环 。 它们都允许您遍历数组,但有一些区别。 让我们详细了解它们。

对于循环 (For loops)

The first and probably most common way to perform a loop is for loops.

执行循环的第一种也是最常见的方法是for循环

Using the for keyword we can define the rules of the loop up front, and then provide the block that is going to be executed repeatedly.

使用for关键字,我们可以预先定义循环规则 ,然后提供将要重复执行的块。

Like this:

像这样:

for (int i = 0; i <= 10; i++) {
  /* instructions to be repeated */
}

The (int i = 0; i <= 10; i++) block contains 3 parts of the looping details:

(int i = 0; i <= 10; i++)块包含3部分循环详细信息:

  • the initial condition (int i = 0)

    初始条件( int i = 0 )

  • the test (i <= 10)

    测试( i <= 10 )

  • the increment (i++)

    增量( i++ )

We first define a loop variable, in this case named i. i is a common variable name to be used for loops, along with j for nested loops (a loop inside another loop). It's just a convention.

我们首先定义一个循环变量,在本例中为ii是用于循环的通用变量名, j是嵌套循环(另一个循环内的循环)的名称。 这只是一个约定。

The variable is initialized at the 0 value, and the first iteration is done. Then it is incremented as the increment part says (i++ in this case, incrementing by 1), and all the cycle repeats until you get to the number 10.

将该变量初始化为0值,并完成第一次迭代。 然后按照增量部分的说明进行递增(在本例中为i++ ,递增1),然后重复所有循环,直到达到数字10。

Inside the loop main block we can access the variable i to know at which iteration we are. This program should print 0 1 2 3 4 5 5 6 7 8 9 10:

在循环主块内部,我们可以访问变量i来知道我们在哪个迭代中。 该程序应打印0 1 2 3 4 5 5 6 7 8 9 10

for (int i = 0; i <= 10; i++) {
  /* instructions to be repeated */
  printf("%u ", i);
}

Loops can also start from a high number, and go a lower number, like this:

循环也可以从较高的数字开始,而从较低的数字开始,如下所示:

for (int i = 10; i > 0; i--) {
  /* instructions to be repeated */
}

You can also increment the loop variable by 2 or another value:

您还可以将循环变量增加2或另一个值:

for (int i = 0; i < 1000; i = i + 30) {
  /* instructions to be repeated */
}

While循环 (While loops)

While loops is simpler to write than a for loop, because it requires a bit more work on your part.

While循环for循环更容易编写,因为它需要您做更多的工作。

Instead of defining all the loop data up front when you start the loop, like you do in the for loop, using while you just check for a condition:

取而代之的定义所有的回路数据前面,当你开始循环,就像你在做for循环使用while你只是检查条件:

while (i < 10) {

}

This assumes that i is already defined and initialized with a value.

假设i已经定义了i并使用值进行了初始化。

And this loop will be an infinite loop unless you increment the i variable at some point inside the loop. An infinite loop is bad because it will block the program, allowing nothing else to happen.

除非您在循环内的某个点增加i变量,否则该循环将是无限循环。 无限循环是不好的,因为它将阻塞程序,从而使其他任何事情都不会发生。

This is what you need for a "correct" while loop:

这是“正确”的while循环所需要的:

int i = 0;

while (i < 10) {
  /* do something */

  i++;
}

There's one exception to this, and we'll see it in one minute. Before, let me introduce do while.

对此有一个例外,我们将在一分钟内看到它。 之前,我先介绍一下do while

做while循环 (Do while loops)

While loops are great, but there might be times when you need to do one particular thing: you want to always execute a block, and then maybe repeat it.

虽然循环是伟大的,但有可能当你需要做一个特别的事情是次:始终要执行的块,然后可能重复。

This is done using the do while keyword. In a way it's very similar to a while loop, but slightly different:

这是使用do while关键字完成的。 在某种程度上,它与while循环非常相似,但略有不同:

int i = 0;

do {
  /* do something */

  i++;
} while (i < 10);

The block that contains the /* do something */ comment is always executed at least once, regardless of the condition check at the bottom.

不管底部是否进行条件检查,包含/* do something */注释的块始终至少执行一次。

Then, until i is less than 10, we'll repeat the block.

然后,直到i小于10,我们将重复该块。

使用break打破循环 (Breaking out of a loop using break)

In all the C loops we have a way to break out of a loop at any point  in time, immediately, regardless of the conditions set for the loop.

在所有C循环中,我们都可以在任何时间点立即退出循环,而不必考虑为循环设置的条件。

This is done using the break keyword.

这是使用break关键字完成的。

This is useful in many cases. You might want to check for the value of a variable, for example:

在许多情况下这很有用。 您可能要检查变量的值,例如:

for (int i = 0; i <= 10; i++) {
  if (i == 4 && someVariable == 10) {
    break;
  }
}

Having this option to break out of a loop is particularly interesting for while loops (and do while too), because we can create seemingly infinite loops that end when a  condition occurs. You define this inside the loop block:

对于while循环(也do while ),具有此选项可以使循环中断特别有趣,因为我们可以创建看似无限的循环,当条件发生时终止。 您可以在循环块中定义此代码:

int i = 0;
while (1) {
  /* do something */

  i++;
  if (i == 10) break;
}

It's rather common to have this kind of loop in C.

在C中有这种循环是相当普遍的。

数组 (Arrays)

An array is a variable that stores multiple values.

数组是存储多个值的变量。

Every value in the array, in C, must have the same type. This means you will have arrays of int values, arrays of double values, and more.

C中的数组中的每个值都必须具有相同的type 。 这意味着您将拥有int值数组, double值数组等等。

You can define an array of int values like this:

您可以像这样定义一个int值数组:

int prices[5];

You must always specify the size of the array. C does not provide dynamic arrays out of the box (you have to use a data structure like a linked list for that).

您必须始终指定数组的大小。 C没有提供开箱即用的动态数组(您必须使用数据结构,例如链表)。

You can use a constant to define the size:

您可以使用常量来定义大小:

const int SIZE = 5;
int prices[SIZE];

You can initialize an array at definition time, like this:

您可以在定义时初始化数组,如下所示:

int prices[5] = { 1, 2, 3, 4, 5 };

But you can also assign a value after the definition, in this way:

但是您也可以通过以下方式在定义之后分配一个值:

int prices[5];

prices[0] = 1;
prices[1] = 2;
prices[2] = 3;
prices[3] = 4;
prices[4] = 5;

Or, more practical, using a loop:

或者,更实际地,使用循环:

int prices[5];

for (int i = 0; i < 5; i++) {
  prices[i] = i + 1;
}

And you can reference an item in the array by using square brackets after the array variable name, adding an integer to determine the index value. Like this:

而且,您可以在数组变量名称之后使用方括号,并添加一个整数以确定索引值,从而引用数组中的项。 像这样:

prices[0]; /* array item value: 1 */
prices[1]; /* array item value: 2 */

Array indexes start from 0, so an array with 5 items, like the prices array above, will have items ranging from prices[0] to prices[4].

数组索引从0开始,因此包含5个项目的数组(如上面的prices数组)将具有从prices[0]prices[4]

The interesting thing about C arrays is that all elements of an array are stored sequentially, one right after another. Not something that normally happens with higher-level programming languages.

关于C数组的有趣之处在于,数组的所有元素都按顺序存储,一个接一个。 高级编程语言通常不会发生这种情况。

Another interesting thing is this: the variable name of the array, prices in the above example, is a pointer to the first element of the array. As such it can be used like a normal pointer.

另一个有趣的事情是:数组的变量名,即上例中的prices ,是指向数组第一个元素的指针 。 因此,它可以像普通指针一样使用。

More on pointers soon.

即将有更多关于指针的信息。

弦乐 (Strings)

In C, strings are one special kind of array: a string is an array of char values:

在C语言中,字符串是一种特殊的数组:字符串是char值的数组:

char name[7];

I introduced the char type when I introduced types, but in short it is commonly used to store letters of the ASCII chart.

当我介绍类型时,我介绍了char类型,但简而言之,它通常用于存储ASCII图表的字母。

A string can be initialized like you initialize a normal array:

可以像初始化普通数组一样初始化字符串:

char name[7] = { "F", "l", "a", "v", "i", "o" };

Or more conveniently with a string literal (also called string constant), a sequence of characters enclosed in double quotes:

或者更方便地使用字符串文字(也称为字符串常量),即用双引号引起来的一系列字符:

char name[7] = "Flavio";

You can print a string via printf() using %s:

您可以使用%s通过printf()打印字符串:

printf("%s", name);

Do you notice how "Flavio" is 6 chars long, but I defined an array of length 7? Why? This is because the last character in a string must be a  0 value, the string terminator, and we must make space for it.

您是否注意到“ Flavio”的长度为6个字符,但是我定义了一个长度为7的数组? 为什么? 这是因为字符串中的最后一个字符必须为0值,即字符串终止符,并且我们必须为其留出空间。

This is important to keep in mind especially when manipulating strings.

记住这一点很重要,尤其是在处理字符串时。

Speaking of manipulating strings, there's one important standard library that is provided by C: string.h.

说到操纵字符串,C: string.h提供了一个重要的标准库。

This library is essential because it abstracts many of the low level details of working with strings, and provides us with a set of useful functions.

这个库是必不可少的,因为它抽象了许多使用字符串的底层细节,并为我们提供了一组有用的功能。

You can load the library in your program by adding on top:

您可以通过在顶部添加来将库加载到程序中:

#include 

And once you do that, you have access to:

完成此操作后,您可以访问:

  • strcpy() to copy a string over another string

    strcpy()将一个字符串复制到另一个字符串上

  • strcat() to append a string to another string

    strcat()将一个字符串附加到另一个字符串

  • strcmp() to compare two strings for equality

    strcmp()比较两个字符串是否相等

  • strncmp() to compare the first n characters of two strings

    strncmp()比较两个字符串的前n字符

  • strlen() to calculate the length of a string

    strlen()计算字符串的长度

and many, many more.

还有很多很多

指针 (Pointers)

Pointers are one of the most confusing/challenging parts of C, in my opinion. Especially if you are new to programming, but also if you come from a higher level programming language like Python or JavaScript.

在我看来,指针是C中最令人困惑/最具挑战性的部分之一。 尤其是如果您不熟悉编程,但是如果您来自更高级别的编程语言(例如Python或JavaScript)。

In this section I want to introduce them in the simplest yet not-dumbed-down way possible.

在本节中,我想以最简单但又不模糊的方式介绍它们。

A pointer is the address of a block of memory that contains a variable.

指针是包含变量的内存块的地址。

When you declare an integer number like this:

当您声明这样的整数时:

int age = 37;

We can use the & operator to get the value of the address in memory of a variable:

我们可以使用&运算符来获取变量内存中的地址值:

printf("%p", &age); /* 0x7ffeef7dcb9c */

I used the %p format specified in printf() to print the address value.

我使用了printf()指定的%p格式来打印地址值。

We can assign the address to a variable:

我们可以将地址分配给变量:

int *address = &age;

Using int *address in the declaration, we are not declaring an integer variable, but rather a pointer to an integer.

在声明中使用int *address ,我们不是在声明整数变量,而是在声明一个指向integer指针

We can use the pointer operator * to get the value of the variable an address is pointing to:

我们可以使用指针运算符*获取地址指向的变量的值:

int age = 37;
int *address = &age;
printf("%u", *address); /* 37 */

This time we are using the pointer operator again, but since it's not a declaration this time it means "the value of the variable this pointer points to".

这次我们再次使用指针运算符,但是由于这一次不是声明,所以它的意思是“该指针指向的变量的值”。

In this example we declare an age variable, and we use a pointer to initialize the value:

在此示例中,我们声明一个age变量,并使用一个指针来初始化该值:

int age;
int *address = &age;
*address = 37;
printf("%u", *address);

When working with C, you'll find that a lot of things are built on top of this simple concept. So make sure you familiarize with it a bit by running the above examples on your own.

使用C时,您会发现很多东西都基于这个简单的概念。 因此,请确保通过自己运行上述示例来稍微熟悉一下。

Pointers are a great opportunity because they force us to think about memory addresses and how data is organized.

指针是一个很好的机会,因为它们迫使我们考虑内存地址以及数据的组织方式。

Arrays are one example. When you declare an array:

数组就是一个例子。 声明数组时:

int prices[3] = { 5, 4, 3 };

The prices variable is actually a pointer to the first item of the array. You can get the value of the first item using this printf() function in this case:

prices变量实际上是指向数组第一项的指针。 在这种情况下,您可以使用此printf()函数获取第一项的值:

printf("%u", *prices); /* 5 */

The cool thing is that we can get the second item by adding 1 to the prices pointer:

很酷的事情是,我们可以通过将prices指针加1来获得第二个项目:

printf("%u", *(prices + 1)); /* 4 */

And so on for all the other values.

对于所有其他值,依此类推。

We can also do many nice string manipulation operations, since strings are arrays under the hood.

我们还可以执行许多不错的字符串操作操作,因为字符串是内部的数组。

We also have many more applications, including passing the reference of an object or a function around to avoid consuming more resources to copy it.

我们还有更多的应用程序,包括传递对象或函数的引用,以避免浪费更多的资源来复制它。

功能 (Functions)

Functions are the way we can structure our code into subroutines that we can:

函数是我们将代码构造为子例程的方式,我们可以:

  1. give a name to

    给...起个名字
  2. call when we need them

    需要我们时打电话

Starting from your very first program, a "Hello, World!", you immediately make use of C functions:

从第一个程序“ Hello,World!”开始,您立即使用C函数:

#include 

int main(void) {
    printf("Hello, World!");
}

The main() function is a very important function, as it's the entry point for a C program.

main()函数是非常重要的函数,因为它是C程序的入口点。

Here's another function:

这是另一个功能:

void doSomething(int value) {
    printf("%u", value);
}

Functions have 4 important aspects:

功能具有四个重要方面:

  1. they have a name, so we can invoke ("call") them later

    它们有一个名字,所以我们以后可以调用(“调用”)它们
  2. they specify a return value

    他们指定一个返回值
  3. they can have arguments

    他们可以争论
  4. they have a body, wrapped in curly braces

    他们有一个身体,用大括号包裹

The function body is the set of instructions that are executed any time we invoke a function.

函数主体是我们每次调用函数时都会执行的指令集。

If the function has no return value, you can use the keyword void before the function name. Otherwise you specify the function return value type (int for an integer, float for a floating point value, const char * for a string, etc).

如果函数没有返回值,则可以在函数名称前使用关键字void 。 否则,您将指定函数的返回值类型(对于整数,为int ;对于浮点值,为float ;对于字符串,则为const char * ,等等)。

You cannot return more than one value from a function.

您不能从一个函数返回多个值。

A function can have arguments. They are optional. If it does not have them, inside the parentheses we insert void, like this:

一个函数可以有参数。 它们是可选的。 如果没有它们,则在括号内插入void ,如下所示:

void doSomething(void) {
   /* ... */
}

In this case, when we invoke the function we'll call it with nothing in the parentheses:

在这种情况下,当我们调用该函数时,将在括号内不加任何名称的情况下调用该函数:

doSomething();

If we have one parameter, we specify the type and the name of the parameter, like this:

如果有一个参数,则指定参数的类型和名称,如下所示:

void doSomething(int value) {
   /* ... */
}

When we invoke the function, we'll pass that parameter in the parentheses, like this:

调用函数时,将在括号中传递该参数,如下所示:

doSomething(3);

We can have multiple parameters, and if so we separate them using a comma, both in the declaration and in the invocation:

我们可以有多个参数,如果这样,我们可以在声明和调用中使用逗号将它们分开:

void doSomething(int value1, int value2) {
   /* ... */
}

doSomething(3, 4);

Parameters are passed by copy. This means that if you modify value1, its value is modified locally. The value outside of the function, where it was passed in the invocation, does not change.

参数通过copy传递。 这意味着,如果您修改value1 ,则其值将在本地修改。 函数外部的值(在调用中传递的值)不会更改。

If you pass a pointer as a parameter, you can modify that variable value because you can now access it directly using its memory address.

如果将指针作为参数传递,则可以修改该变量值,因为现在可以使用其内存地址直接访问它。

You can't define a default value for a parameter. C++ can do that (and so Arduino Language programs can), but C can't.

您无法为参数定义默认值。 C ++可以做到这一点(Arduino语言程序也可以做到),但是C不能。

Make sure you define the function before calling it, or the compiler will raise a warning and an error:

确保在调用函数之前定义了该函数,否则编译器将引发警告和错误:

➜  ~ 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.

The warning you get regards the ordering, which I already mentioned.

您收到的警告与订购有关,我已经提到过。

The error is about another thing, related. Since C does not "see" the function declaration before the invocation, it must make assumptions. And it assumes the function to return int. The function however returns void, hence the error.

该错误与另一件事有关。 由于C在调用之前没有“看到”函数声明,因此它必须进行假设。 并且它假定函数返回int 。 但是该函数返回void ,因此返回错误。

If you change the function definition to:

如果将函数定义更改为:

int doSomething(int value1, int value2) {
  printf("%d %d\n", value1, value2);
  return 1;
}

you'd just get the warning, and not the error:

您只会得到警告,而不是错误:

➜  ~ 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.

In any case, make sure you declare the function before using it. Either move the function up, or add the function prototype in a header file.

无论如何,请确保在使用该函数之前先声明该函数。 向上移动函数,或将函数原型添加到头文件中。

Inside a function, you can declare variables.

在函数内部,您可以声明变量。

void doSomething(int value) {
  int doubleValue = value * 2;
}

A variable is created at the point of invocation of the function and is destroyed when the function ends. It's not visible from the outside.

变量在函数调用时创建,并在函数结束时销毁。 从外面看不到。

Inside a function, you can call the function itself. This is called recursion and it's something that offers peculiar opportunities.

在函数内部,您可以调用函数本身。 这称为递归 ,它提供了特殊的机会。

输入输出 (Input and output)

C is a small language, and the "core" of C does not include any Input/Output (I/O) functionality.

C是一种小语言,C的“核心”不包含任何输入/输出(I / O)功能。

This is not something unique to C, of course. It's common for the language core to be agnostic of I/O.

当然,这不是C独有的。 语言核心通常与I / O无关。

In the case of C, Input/Output is provided to us by the C Standard Library via a set of functions defined in the stdio.h header file.

对于C语言,C标准库通过stdio.h头文件中定义的一组函数为我们提供了输入/输出。

You can import this library using:

您可以使用以下命令导入该库:

#include 

on top of your C file.

在您的C文件之上。

This library provides us with, among many other functions:

该库为我们提供了许多其他功能:

  • printf()

    printf()

  • scanf()

    scanf()

  • sscanf()

    sscanf()

  • fgets()

    fgets()

  • fprintf()

    fprintf()

Before describing what those functions do, I want to take a minute to talk about I/O streams.

在描述这些功能之前,我想花一点时间讨论一下I / O流

We have 3 kinds of I/O streams in C:

我们在C中有3种I / O流:

  • stdin (standard input)

    stdin (标准输入)

  • stdout (standard output)

    stdout (标准输出)

  • stderr (standard error)

    stderr (标准错误)

With I/O functions we always work with streams. A stream is a high level interface that can represent a device or a file. From the C standpoint, we don't have any difference in reading from a file or reading from the command line: it's an I/O stream in any case.

使用I / O功能,我们始终可以处理流。 流是可以代表设备或文件的高级接口。 从C的角度来看,从文件读取或从命令行读取没有任何区别:无论如何,它都是I / O流。

That's one thing to keep in mind.

这是要记住的一件事。

Some functions are designed to work with a specific stream, like printf(), which we use to print characters to stdout. Using its more general counterpart fprintf(), we can specify which stream to write to.

一些函数被设计为与特定的流一起使用,例如printf() ,我们将其用于将字符打印到stdout 。 使用更通用的对应fprintf() ,我们可以指定要写入的流。

Since I started talking about printf(), let's introduce it now.

自从我开始谈论printf()以来,现在让我们对其进行介绍。

printf() is one of the first functions you'll use when learning C programming.

printf()是学习C编程时要使用的第一个功能之一。

In its simplest usage form, you pass it a string literal:

在其最简单的用法形式中,您为它传递了字符串文字:

printf("hey!");

and the program will print the content of the string to the screen.

然后程序会将字符串的内容打印到屏幕上。

You can print the value of a variable. But it's a bit tricky because you need to add a special character, a placeholder, which changes depending on the type of the variable. For example we use %d for a signed decimal integer digit:

您可以打印变量的值。 但这有点棘手,因为您需要添加一个特殊字符(占位符),该字符会根据变量的类型而变化。 例如,我们将%d用于带符号的十进制整数:

int age = 37;

printf("My age is %d", age);

We can print more than one variable by using commas:

我们可以使用逗号来打印多个变量:

int age_yesterday = 37;
int age_today = 36;

printf("Yesterday my age was %d and today is %d", age_yesterday, age_today);

There are other format specifiers like %d:

还有其他格式说明符,例如%d

  • %c for a char

    %c个字符

  • %s for a char

    %s为一个字符

  • %f for floating point numbers

    %f用于浮点数

  • %p for pointers

    %p的指针

and many more.

还有很多。

We can use escape characters in printf(), like \n which we can use to make the output create a new line.

我们可以在printf()使用转义字符,例如\n ,以便使输出创建新行。

scanf() (scanf())

printf() is used as an output function. I want to introduce an input function now, so we can say we can do all the I/O thing: scanf().

printf()用作输出函数。 我现在想介绍一个输入函数,因此可以说我们可以完成所有I / O事情: scanf()

This function is used to get a value from the user running the program, from the command line.

此函数用于从命令行从运行程序的用户获取值。

We must first define a variable that will hold the value we get from the input:

我们必须首先定义一个变量,该变量将保存从输入中获得的值:

int age;

Then we call scanf() with 2 arguments: the format (type) of the variable, and the address of the variable:

然后我们用两个参数调用scanf() :变量的格式(类型)和变量的地址:

scanf("%d", &age);

If we want to get a string as input, remember that a string name is a pointer to the first character, so you don't need the & character before it:

如果要获取字符串作为输入,请记住字符串名称是指向第一个字符的指针,因此您不需要在&字符之前:

char name[20];
scanf("%s", name);

Here's a little program that uses both printf() and scanf():

这是一个同时使用printf()scanf()的小程序:

#include 

int main(void) {
  char name[20];
  printf("Enter your name: ");
  scanf("%s", name);
  printf("you entered %s", name);
}

可变范围 (Variable scope)

When you define a variable in a C program, depending on where you declare it, it will have a different scope.

在C程序中定义变量时,根据声明位​​置的不同,它的作用域也不同。

This means that it will be available in some places, but not in others.

这意味着它将在某些地方可用,但在其他地方则不可用。

The position determines 2 types of variables:

该位置确定2种类型的变量:

  • global variables

    全局变量

  • local variables

    局部变量

This is the difference: a variable declared inside a function is a local variable, like this:

区别在于:在函数内部声明的变量是局部变量,如下所示:

int main(void) {
  int age = 37;
}

Local variables are only accessible from within the function, and when the function ends they stop their existence. They are cleared from the memory (with some exceptions).

局部变量只能从函数内部访问,当函数结束时,它们将停止存在。 它们从内存中清除(有些例外)。

A variable defined outside a function is a global variable, like in this example:

在函数外部定义的变量是全局变量,例如以下示例:

int age = 37;

int main(void) {
  /* ... */
}

Global variables are accessible from any function of the program, and they are available for the whole execution of the program, until it ends.

全局变量可从程序的任何功能访问,并且在整个程序执行之前可用,直到结束。

I mentioned that local variables are not available any more after the function ends.

我提到函数结束后,局部变量不再可用。

The reason is that local variables are declared on the stack, by default, unless you explicitly allocate them on the heap using pointers. But then you have to manage the memory yourself.

原因是默认情况下局部变量在堆栈上声明,除非您使用指针在堆上显式分配它们。 但是然后您必须自己管理内存。

静态变量 (Static variables)

Inside a function, you can initialize a static variable using the static keyword.

在函数内部,可以使用static关键字初始化静态变量

I said "inside a function" because global variables are static by default, so there's no need to add the keyword.

我说“在函数内部”是因为默认情况下全局变量是静态的,因此无需添加关键字。

What's a static variable? A static variable is initialized to 0 if no initial value is specified, and it retains the value across function calls.

什么是静态变量? 如果未指定初始值,则静态变量将初始化为0,并在函数调用期间保留该值。

Consider this function:

考虑以下功能:

int incrementAge() {
  int age = 0;
  age++;
  return age;
}

If we call incrementAge() once, we'll get 1 as the return value. If we call it more than once, we'll always get 1 back, because age is a local variable and it's re-initialized to 0 on every single function call.

如果我们调用一次incrementAge() ,我们将获得1作为返回值。 如果我们多次调用它,我们总是会得到1,因为age是一个局部变量,并且在每次调用函数时都会将其重新初始化为0

If we change the function to:

If we change the function to:

int incrementAge() {
  static int age = 0;
  age++;
  return age;
}

Now every time we call this function, we'll get an incremented value:

Now every time we call this function, we'll get an incremented value:

printf("%d\n", incrementAge());
printf("%d\n", incrementAge());
printf("%d\n", incrementAge());

will give us

will give us

1
2
3

We can also omit initializing age to 0 in static int age = 0;, and just write static int age; because static variables are automatically set to 0 when created.

We can also omit initializing age to 0 in static int age = 0; , and just write static int age; because static variables are automatically set to 0 when created.

We can also have static arrays. In this case, each single item in the array is initialized to 0:

We can also have static arrays. In this case, each single item in the array is initialized to 0:

int incrementAge() {
  static int ages[3];
  ages[0]++;
  return ages[0];
}

Global variables (Global variables)

In this section I want to talk more about the difference between global and local variables.

In this section I want to talk more about the difference between global and local variables .

A local variable is defined inside a function, and it's only available inside that function.

A local variable is defined inside a function, and it's only available inside that function.

Like this:

像这样:

#include 

int main(void) {
  char j = 0;
  j += 10;
  printf("%u", j); //10
}

j is not available anywhere outside the main function.

j is not available anywhere outside the main function.

A global variable is defined outside of any function, like this:

A global variable is defined outside of any function, like this:

#include 

char i = 0;

int main(void) {
  i += 10;
  printf("%u", i); //10
}

A global variable can be accessed by any function in the program.  Access is not limited to reading the value: the variable can be updated by any function.

A global variable can be accessed by any function in the program. Access is not limited to reading the value: the variable can be updated by any function.

Due to this, global variables are one way we have of sharing the same data between functions.

Due to this, global variables are one way we have of sharing the same data between functions.

The main difference with local variables is that the memory allocated for variables is freed once the function ends.

The main difference with local variables is that the memory allocated for variables is freed once the function ends.

Global variables are only freed when the program ends.

Global variables are only freed when the program ends.

Type definitions (Type definitions)

The typedef keyword in C allows you to defined new types.

The typedef keyword in C allows you to defined new types.

Starting from the built-in C types, we can create our own types, using this syntax:

Starting from the built-in C types, we can create our own types, using this syntax:

typedef existingtype NEWTYPE

The new type we create is usually, by convention, uppercase.

The new type we create is usually, by convention, uppercase.

This it to distinguish it more easily, and immediately recognize it as type.

This it to distinguish it more easily, and immediately recognize it as type.

For example we can define a new NUMBER type that is an int:

For example we can define a new NUMBER type that is an int :

typedef int NUMBER

and once you do so, you can define new NUMBER variables:

and once you do so, you can define new NUMBER variables:

NUMBER one = 1;

Now you might ask: why? Why not just use the built-in type int instead?

Now you might ask: why? Why not just use the built-in type int instead?

Well, typedef gets really useful when paired with two things: enumerated types and structures.

Well, typedef gets really useful when paired with two things: enumerated types and structures.

Enumerated types (Enumerated types)

Using the typedef and enum keywords we can define a type that can have either one value or another.

Using the typedef and enum keywords we can define a type that can have either one value or another.

It's one of the most important uses of the typedef keyword.

It's one of the most important uses of the typedef keyword.

This is the syntax of an enumerated type:

This is the syntax of an enumerated type:

typedef enum {
  //...values
} TYPENAME;

The enumerated type we create is usually, by convention, uppercase.

The enumerated type we create is usually, by convention, uppercase.

Here is a simple example:

Here is a simple example:

typedef enum {
  true,
  false
} BOOLEAN;

C comes with a bool type, so this example is not really practical, but you get the idea.

C comes with a bool type, so this example is not really practical, but you get the idea.

Another example is to define weekdays:

Another example is to define weekdays:

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

Here's a simple program that uses this enumerated type:

Here's a simple program that uses this enumerated type:

#include 

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"); 
  }
}

Every item in the enum definition is paired to an integer, internally. So in this example monday is 0, tuesday is 1 and so on.

Every item in the enum definition is paired to an integer, internally. So in this example monday is 0, tuesday is 1 and so on.

This means the conditional could have been if (day == 0) instead of if (day == monday), but it's way simpler for us humans to reason with names rather than numbers, so it's a very convenient syntax.

This means the conditional could have been if (day == 0) instead of if (day == monday) , but it's way simpler for us humans to reason with names rather than numbers, so it's a very convenient syntax.

结构体 (Structures)

Using the struct keyword we can create complex data structures using basic C types.

Using the struct keyword we can create complex data structures using basic C types.

A structure is a collection of values of different types. Arrays in C are limited to a type, so structures can prove to be very interesting in a lot of use cases.

A structure is a collection of values of different types. Arrays in C are limited to a type, so structures can prove to be very interesting in a lot of use cases.

This is the syntax of a structure:

This is the syntax of a structure:

struct  {
  //...variables
};

Example:

例:

struct person {
  int age;
  char *name;
};

You can declare variables that have as type that structure by adding them after the closing curly bracket, before the semicolon, like this:

You can declare variables that have as type that structure by adding them after the closing curly bracket, before the semicolon, like this:

struct person {
  int age;
  char *name;
} flavio;

Or multiple ones, like this:

Or multiple ones, like this:

struct person {
  int age;
  char *name;
} flavio, people[20];

In this case I declare a single person variable named flavio, and an array of 20 person named people.

In this case I declare a single person variable named flavio , and an array of 20 person named people .

We can also declare variables later on, using this syntax:

We can also declare variables later on, using this syntax:

struct person {
  int age;
  char *name;
};

struct person flavio;

We can initialize a structure at declaration time:

We can initialize a structure at declaration time:

struct person {
  int age;
  char *name;
};

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

and once we have a structure defined, we can access the values in it using a dot:

and once we have a structure defined, we can access the values in it using a dot:

struct person {
  int age;
  char *name;
};

struct person flavio = { 37, "Flavio" };
printf("%s, age %u", flavio.name, flavio.age);

We can also change the values using the dot syntax:

We can also change the values using the dot syntax:

struct person {
  int age;
  char *name;
};

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

flavio.age = 38;

Structures are very useful because we can pass them around as function parameters, or return values, embedding various variables within them. Each variable has a label.

Structures are very useful because we can pass them around as function parameters, or return values, embedding various variables within them. Each variable has a label.

It's important to note that structures are passed by copy, unless of course you pass a pointer to a struct, in which case it's passed by reference.

It's important to note that structures are passed by copy , unless of course you pass a pointer to a struct, in which case it's passed by reference.

Using typedef we can simplify the code when working with structures.

Using typedef we can simplify the code when working with structures.

Let's look at an example:

让我们看一个例子:

typedef struct {
  int age;
  char *name;
} PERSON;

The structure we create using typedef is usually, by convention, uppercase.

The structure we create using typedef is usually, by convention, uppercase.

Now we can declare new PERSON variables like this:

Now we can declare new PERSON variables like this:

PERSON flavio;

and we can initialize them at declaration in this way:

and we can initialize them at declaration in this way:

PERSON flavio = { 37, "Flavio" };

Command line parameters (Command line parameters)

In your C programs, you might need to accept parameters from the command line when the command launches.

In your C programs, you might need to accept parameters from the command line when the command launches.

For simple needs, all you need to do to do so is change the main() function signature from

For simple needs, all you need to do to do so is change the main() function signature from

int main(void)

to

int main (int argc, char *argv[])

argc is an integer number that contains the number of parameters that were provided in the command line.

argc is an integer number that contains the number of parameters that were provided in the command line.

argv is an array of strings.

argv is an array of strings.

When the program starts, we are provided the arguments in those 2 parameters.

When the program starts, we are provided the arguments in those 2 parameters.

Note that there's always at least one item in the argv array: the name of the program

Note that there's always at least one item in the argv array: the name of the program

Let's take the example of the C compiler we use to run our programs, like this:

Let's take the example of the C compiler we use to run our programs, like this:

gcc hello.c -o hello

If this was our program, we'd have argc being 4 and argv being an array containing

If this was our program, we'd have argc being 4 and argv being an array containing

  • gcc

    gcc

  • hello.c

    hello.c

  • -o

    -o

  • hello

    hello

Let's write a program that prints the arguments it receives:

Let's write a program that prints the arguments it receives:

#include 

int main (int argc, char *argv[]) {
  for (int i = 0; i < argc; i++) {
    printf("%s\n", argv[i]);
  }
}

If the name of our program is hello and we run it like this: ./hello, we'd get this as output:

If the name of our program is hello and we run it like this: ./hello , we'd get this as output:

./hello

If we pass some random parameters, like this: ./hello a b c we'd get this output to the terminal:

If we pass some random parameters, like this: ./hello abc we'd get this output to the terminal:

./hello
a
b
c

This system works great for simple needs. For more complex needs, there are commonly used packages like getopt.

This system works great for simple needs. For more complex needs, there are commonly used packages like getopt .

Headers files (Headers files)

Simple programs can be put in a single file. But when your program grows larger it's impossible to keep it all in just one file.

Simple programs can be put in a single file. But when your program grows larger it's impossible to keep it all in just one file.

You can move parts of a program to a separate file. Then you create a header file.

You can move parts of a program to a separate file. Then you create a header file .

A header file looks like a normal C file, except it ends with .h instead of .c. Instead of the implementations of your functions and the other parts of a program, it holds the declarations.

A header file looks like a normal C file, except it ends with .h instead of .c . Instead of the implementations of your functions and the other parts of a program, it holds the declarations .

You already used header files when you first used the printf() function, or other I/O function, and you had to type:

You already used header files when you first used the printf() function, or other I/O function, and you had to type:

#include 

to use it.

to use it.

#include is a preprocessor directive.

#include is a preprocessor directive.

The preprocessor goes and looks up the stdio.h file in the standard library because you used brackets around it. To include your own header files, you'll use quotes, like this:

The preprocessor goes and looks up the stdio.h file in the standard library because you used brackets around it. To include your own header files, you'll use quotes, like this:

#include "myfile.h"

The above will look up myfile.h in the current folder.

The above will look up myfile.h in the current folder.

You can also use a folder structure for libraries:

You can also use a folder structure for libraries:

#include "myfolder/myfile.h"

Let's look at an example. This program calculates the years since a given year:

让我们来看一个例子。 This program calculates the years since a given year:

#include 

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

int main(void) {
  printf("%u", calculateAge(1983));
}

Suppose I want to move the calculateAge function to a separate file.

Suppose I want to move the calculateAge function to a separate file.

I create a calculate_age.c file:

I create a calculate_age.c file:

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

And a calculate_age.h file where I put the function prototype, which is the same as the function in the .c file, except the body:

And a calculate_age.h file where I put the function prototype , which is the same as the function in the .c file, except the body:

int calculateAge(int year);

Now in the main .c file we can go and remove the calculateAge() function definition, and we can import calculate_age.h, which will make the calculateAge() function available:

Now in the main .c file we can go and remove the calculateAge() function definition, and we can import calculate_age.h , which will make the calculateAge() function available:

#include 
#include "calculate_age.h"

int main(void) {
  printf("%u", calculateAge(1983));
}

Don't forget that to compile a program composed by multiple files, you need to list them all in the command line, like this:

Don't forget that to compile a program composed by multiple files, you need to list them all in the command line, like this:

gcc -o main main.c calculate_age.c

And with more complex setups, a Makefile is necessary to tell the compiler how to compile the program.

And with more complex setups, a Makefile is necessary to tell the compiler how to compile the program.

The preprocessor (The preprocessor)

The preprocessor is a tool that helps us a lot when programming with C. It is part of the C Standard, just like the language, the compiler, and the standard library.

The preprocessor is a tool that helps us a lot when programming with C. It is part of the C Standard, just like the language, the compiler, and the standard library.

It parses our program and makes sure that the compiler gets all the things it needs before going on with the process.

It parses our program and makes sure that the compiler gets all the things it needs before going on with the process.

What does it do, in practice?

What does it do, in practice?

For example, it looks up all the header files you include with the #include directive.

For example, it looks up all the header files you include with the #include directive.

It also looks at every constant you defined using #define and substitutes it with its actual value.

It also looks at every constant you defined using #define and substitutes it with its actual value.

That's just the start. I mentioned those 2 operations because they are the most common ones. The preprocessor can do a lot more.

这仅仅是开始。 I mentioned those 2 operations because they are the most common ones. The preprocessor can do a lot more.

Did you notice #include and #define have a # at the beginning? That's common to all the preprocessor directives. If a line starts with #, that's taken care of by the preprocessor.

Did you notice #include and #define have a # at the beginning? That's common to all the preprocessor directives. If a line starts with # , that's taken care of by the preprocessor.

Conditionals (Conditionals)

One of the things we can do is to use conditionals to change how our program will be compiled, depending on the value of an expression.

One of the things we can do is to use conditionals to change how our program will be compiled, depending on the value of an expression.

For example we can check if the DEBUG constant is 0:

For example we can check if the DEBUG constant is 0:

#include 

const int DEBUG = 0;

int main(void) {
#if DEBUG == 0
  printf("I am NOT debugging\n");
#else
  printf("I am debugging\n");
#endif
}

Symbolic constants (Symbolic constants)

We can define a symbolic constant:

We can define a symbolic constant :

#define VALUE 1
#define PI 3.14
#define NAME "Flavio"

When we use NAME or PI or VALUE in our program, the preprocessor replaces its name with the value before executing the program.

When we use NAME or PI or VALUE in our program, the preprocessor replaces its name with the value before executing the program.

Symbolic constants are very useful because we can give names to values without creating variables at compilation time.

Symbolic constants are very useful because we can give names to values without creating variables at compilation time.

Macros (Macros)

With #define we can also define a macro. The difference between a macro and a symbolic constant is that a macro can accept an argument and typically contains code, while a symbolic constant is a value:

With #define we can also define a macro . The difference between a macro and a symbolic constant is that a macro can accept an argument and typically contains code, while a symbolic constant is a value:

#define POWER(x) ((x) * (x))

Notice the parentheses around the arguments: this is a good practice to avoid issues when the macro is replaced in the precompilation process.

Notice the parentheses around the arguments: this is a good practice to avoid issues when the macro is replaced in the precompilation process.

Then we can use it in our code like this:

Then we can use it in our code like this:

printf("%u\n", POWER(4)); //16

The big difference with functions is that macros do not specify the type of their arguments or return values, which might be handy in some cases.

The big difference with functions is that macros do not specify the type of their arguments or return values, which might be handy in some cases.

Macros, however, are limited to one line definitions.

Macros, however, are limited to one line definitions.

If defined (If defined)

We can check if a symbolic constant or a macro is defined using #ifdef:

We can check if a symbolic constant or a macro is defined using #ifdef :

#include 
#define VALUE 1

int main(void) {
#ifdef VALUE
  printf("Value is defined\n");
#else
  printf("Value is not defined\n");
#endif
}

We also have #ifndev to check for the opposite (macro not defined).

We also have #ifndev to check for the opposite (macro not defined).

We can also use #if defined and #if !defined to do the same task.

We can also use #if defined and #if !defined to do the same task.

It's common to wrap some block of code into a block like this:

It's common to wrap some block of code into a block like this:

#if 0

#endif

to temporarily prevent it from running, or to use a DEBUG symbolic constant:

to temporarily prevent it from running, or to use a DEBUG symbolic constant:

#define DEBUG 0

#if DEBUG
  //code only sent to the compiler
  //if DEBUG is not 0
#endif

Predefined symbolic constants you can use (Predefined symbolic constants you can use)

The preprocessor also defines a number of symbolic constants you can use, identified by the 2 underscores before and after the name, including:

The preprocessor also defines a number of symbolic constants you can use, identified by the 2 underscores before and after the name, including:

  • __LINE__ translates to the current line in the source code file

    __LINE__ translates to the current line in the source code file

  • __FILE__ translates to the name of the file

    __FILE__ translates to the name of the file

  • __DATE__ translates to the compilation date, in the Mmm gg aaaa format

    __DATE__ translates to the compilation date, in the Mmm gg aaaa format

  • __TIME__ translates to the compilation time, in the hh:mm:ss format

    __TIME__ translates to the compilation time, in the hh:mm:ss format

结论 (Conclusion)

Thanks a lot for reading this handbook!

Thanks a lot for reading this handbook!

I hope it will inspire you to know more about C.

I hope it will inspire you to know more about C.

For more tutorials, check out my blog flaviocopes.com.

For more tutorials, check out my blog flaviocopes.com .

Send any feedback, errata, or opinions at [email protected]

Send any feedback, errata, or opinions at [email protected]

And remember: You can get a PDF and ePub version of this C Beginner's Handbook

And remember: You can get a PDF and ePub version of this C Beginner's Handbook

You can reach me on Twitter @flaviocopes.

You can reach me on Twitter @flaviocopes .

翻译自: https://www.freecodecamp.org/news/the-c-beginners-handbook/

初学者c语言编程软件

你可能感兴趣的:(初学者c语言编程软件_C初学者手册:仅需几个小时即可学习C编程语言基础知识)