跳至主要內容

01-C 语言基础概念

AI悦创原创C教程C教程大约 43 分钟...约 12836 字

1. 什么是 C 语言

C 是一种通用的编程语言,已有近 50 年的历史。 C 可以编写从操作系统(包括 Windows 和其他许多系统)到复杂的程序,如 Python 解释器、Git、Oracle 数据库等。

C 的功能性是设计出来的。它是一种低级别的语言,与机器的工作方式密切相关,同时也易于学习。

学习 C 语言,可以帮助大家更深入理解计算机底层的工作原理,如内存管理。

C语言是?

2. Hello World!

如同学习任何新语言一样,让我们从经典的 "Hello World!"程序开始:

1
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

现在,分析下上面的代码:

  • #include <stdio.h> 引入头文件 stdio.h。为了使用 printf 函数,需要首先包括所需的文件,也称为头文件
  • int main() main() 函数是一个程序的执行入口。大括号{ }表示一个函数(也叫代码块)的开始和结束。大括号内的语句决定了该函数在执行时的作用。

2.1 探究 c include 的功能性

  1. 首先,打开 Terminal。
  2. 使用 cd 命令来切换到你的源代码所在的目录。
  3. 使用 clang 命令来编译你的源代码。比如,如果你的源代码文件名是 main.c,你可以使用以下命令来编译:
clang -E main.c > test.i
  • -E 参数会让 clang 只预处理源代码,并把结果输出到 test.i 文件中。

如果你是想要编译 C++ 程序,你可以把 clang 改成 clang++,其他步骤是一样的。

注意这只是预处理你的代码,并没有完成编译过程。如果你想要编译并执行你的代码,你需要更多的步骤。

填空,引入头文件

#include <___.h>

2.2 printf 函数用来输出内容

use \n
#include <stdio.h>

int main() {
    // 输出内容
    printf("Hello, World!\n");
    // 返回 0,表示成功
    return 0;
}

这里,我们把文本 "Hello World!"传给它。 转义字符\nescape sequence)输出一个换行符。转义字符总是以反斜线 \ 开始。

分号;表示语句的结束。每个语句必须以分号;结束。

return 0; 语句则终止了 main() 函数,并返回数值0。数字0通常意味着我们的程序已经成功执行。任何其他数字都表示程序失败了。

2.3 练习

打印文本"Hi, everyone!"

3. 数据类型 Data Type

C 语言支持以下基本数据类型:

  1. int: 整数,一个整数。
  2. float: 浮点数,一个带有小数部分的数字。
  3. double: 双精度浮点数。
  4. char: 单字符。

每种类型所需的存储大小因平台而异。

C 语言有一个内置的 sizeof 操作符,可以返回特定数据类型的内存占用大小。 例如:

code
#include <stdio.h>

int main() {
    printf("int: %ld \n", sizeof(int));
    printf("float: %ld \n", sizeof(float));
    printf("double: %ld \n", sizeof(double));
    printf("char: %ld \n", sizeof(char));

    return 0;
}

程序输出显示每种数据类型的相应大小(字节)。

本程序中的 printf 语句有两个参数

  • 第1个参数是带有格式指定符%ld)的字符串
  • 第2个参数返回 sizeof 值。

在最后的输出中,%ld(用于长十进制)被第 2 个参数中的值取代。

注意

请注意,C 语言没有布尔类型。「较早的版本是没有的」

一个 printf 语句可以有多个格式指定符,并有相应的参数来替换指定符。

我们将在接下来的课程中学习更多关于格式指定符的知识。

练习: 下面哪一项是 C 语言中的正确的变量类型?「C」

A. int, double, char, boolean

B. int, bool, string

C. int, float, double, char

D. int, float, string, char

4. 变量 Variable

变量是内存中某块值的名称。

变量名(也称为标识符)必须以字母或下划线_开头,可以由字母、数字和下划线_字符组成。

不同语言中,变量的命名规则各不相同,但是使用小写字母和下划线来分隔单词是很常见的(snake_case)。

变量在使用前也必须声明为一种数据类型。

赋值语句可以改变已声明的变量的值。 例如,下面的语句声明了一个整数变量 my_var,然后将其赋值为 42。

int my_var;
my_var = 42;

你也可以在一条语句中同时声明和初始化变量的量。

int my_var = 42;

snake_case 是一种命名约定,通常用于编程语言中的变量、函数或其他标识符的命名。这种命名方式的特点是所有的单词都是小写,并且单词之间用下划线(_)来分隔。

snake_case 得名于它的外观——单词之间的下划线看起来就像是蛇的弯曲身体。这种命名方式的优点是清晰易读,特别是对于由多个单词组成的名称。

以下是一些 snake_case 命名的例子:

  • employee_name
  • total_amount
  • print_employee_details
  • calculate_average_score

在一些编程语言和框架中,snake_case 是推荐或者必须的命名方式。例如,Python 和 Ruby 的社区通常推荐使用 snake_case 来命名变量和函数。在 SQL 语言中,snake_case 也是常见的命名方式。

然而,不同的编程语言、框架或项目可能会有不同的命名约定。在 JavaScript 或 Java 中,常见的命名方式是 camelCase,而在 C# 中,常见的命名方式是 PascalCase。选择哪种命名方式取决于你所使用的编程语言和项目的编码规范。

Other

在 C 语言中,变量声明的基本形式如下:

type variable_name;

其中,type 是变量的类型,variable_name 是变量名。例如:

int a;       // 声明一个整型变量a
float b;     // 声明一个浮点型变量b
char c;      // 声明一个字符型变量c

你也可以在声明的同时初始化变量:

int a = 10;        // 声明并初始化整型变量a为10
float b = 3.14;    // 声明并初始化浮点型变量b为3.14
char c = 'z';      // 声明并初始化字符型变量c为'z'

如果你想同时声明多个相同类型的变量,可以这样做:

int a, b, c;      // 声明多个整型变量a, b, c
float x, y, z;    // 声明多个浮点型变量x, y, z

对于数组类型的变量,你可以这样声明:

int array[10];    // 声明一个有10个元素的整型数组

对于指针类型的变量,你可以这样声明:

int *p;    // 声明一个整型指针p

这些只是最常见的例子,C 语言的变量类型还包括复合类型(如结构体和联合)、枚举类型等,它们的声明方式略有不同。总的来说,C 语言中变量的声明方法多种多样,可以根据你的需求选择合适的类型和声明方式。


在 C 语言中,你可以在一行中声明多个变量,并为每个变量分别赋值。示例如下:

int a = 10, b = 20, c = 30;

在这个例子中,我们声明了三个整型变量 abc,并分别初始化它们的值为 10、20 和 30。

你也可以为多个数组元素同时赋值,例如:

int array[3] = {10, 20, 30};

在这个例子中,我们声明了一个包含三个元素的整型数组 array,并分别初始化数组元素的值为 10、20 和 30。

需要注意的是,所有变量的类型必须是一样的,即你不能在一行中声明不同类型的变量。例如,以下的代码是无效的:

int a = 10, float b = 20.0;  // 错误!不可以在一行中声明不同类型的变量

让我们定义不同类型的变量,做一个简单的数学运算,并输出结果:

声明
#include <stdio.h>

int main() {
    int a, b;               // 声明整型变量 a 和 b
    float salary = 56.23;   // 声明浮点型变量 salary 并赋值为 56.23
    char letter = 'Z';      // 声明字符型变量 letter 并赋值为 'Z'
    a = 8;                  // 将变量 a 赋值为 8
    b = 34;                 // 将变量 b 赋值为 34
    int c = a + b;            // 声明整型变量 c 并将 a 和 b 的和赋值给 c

    printf("%d \n", c);     // 打印变量 c 的值
    printf("%f \n", salary);    // 打印变量 salary 的值
    printf("%c \n", letter);    // 打印变量 letter 的值

    return 0;
}

正如你所见,可以在一行中声明多个变量,用逗号,分隔它们。

另外,注意对浮点数(%f)和字符(%c)输出使用不同的格式指定符。

注意

C 编程语言是大小写敏感的,所以 my_Variablemy_variable 是两个不同的标识。

练习:

填空,声明一个 int 类型变量 num,并赋值为 42。

5. 常量 Constant

一个常量存储了一个不能改变值的变量(但必须初始化变量)。

通过使用有意义的常量名,代码会更易阅读和理解。 为了区分常量和变量,一个常见的做法是使用大写的标识符来声明常量。

定义常量的一种方法是在变量声明前使用 const 关键字。

#include <stdio.h>

int main() {
    // const 声明为常量并初始化值
    const double PI = 3.14;
    printf("%f", PI);
    
    return 0;
}

在程序执行期间,常量 PI 的值不能被改变。

例如,PI=3.141将产生一个错误。

注意

常量在声明时必须用一个值进行初始化,因为常量在创建出来之后,不能被修改。所以,一开始如果不赋值的话,在后面你就无法赋值了。

另一种定义常量的方法是使用 #define 预处理器指令。

#define 指令使用宏来定义常量值。 例如:

#include <stdio.h>

#define PI 3.14

int main() {
    printf("%f", PI);
    return 0;
}
const & define

constdefine 在 C 语言中都可以用来定义常量,但是它们的工作方式有所不同,并且各有各的用途。以下是它们之间的一些主要区别:

  1. 预处理器和编译器define 是预处理器指令,它在编译过程的预处理阶段进行替换,而 const 是编译器指令,它在编译阶段进行处理。

  2. 作用域define 不考虑作用域,只要在定义之后,都可以使用。而 const 具有作用域,只在定义它的作用域内有效。

  3. 内存占用define 定义的常量不会占用内存,因为它们在预处理阶段就被替换了。但是 const 定义的常量会占用内存,因为它们在运行时还存在。

  4. 类型和检查define 定义的常量没有类型,它只是简单的文本替换,编译器无法进行类型检查。但 const 定义的常量有类型,编译器会进行类型检查。

现在我们来看一些代码示例:

使用 define

#define PI 3.14159

int main() {
    float radius = 5.0;
    float area = PI * radius * radius;
    printf("Area: %f\n", area);
    return 0;
}

在上面的代码中,PI 被定义为 3.14159,并在计算面积时使用。

使用 const

int main() {
    const float pi = 3.14159;
    float radius = 5.0;
    float area = pi * radius * radius;
    printf("Area: %f\n", area);
    return 0;
}

在这个代码中,pi 是一个 const 常量,也被用来计算面积。注意,pi 有一个类型(float),并且只在 main 函数的作用域内有效。

在编译之前,预处理程序将代码中的每个宏标识符替换为指令中的相应值。在这种情况下,每个出现的 PI 都被替换为 3.14。 发送给编译器的最终代码将已经有了常量值。

const#define的区别在于,前者使用内存进行存储,而后者不使用。

Question 1

在具体使用的时候我们应该如何选择?选择依据是什么?

选择 const 或者 define 的决定依赖于具体的应用场景和你的需求。以下是一些考虑的因素:

  1. 类型检查:如果你需要编译器进行类型检查,你应该选择 const,因为 define 不提供类型检查。如果你试图使用一个与定义的类型不同的值,使用 const 的代码会在编译时报错。

  2. 作用域:如果你希望常量只在特定的作用域内有效,你应该使用 const,因为它会考虑作用域。define 定义的宏在定义后的所有地方都是有效的。

  3. 性能:由于 define 在预处理阶段就进行了文本替换,所以它不会占用内存,这可能会有一些性能优势。然而,这个优势通常很小,并且在现代计算机上可能无法察觉。

  4. 编程风格:在一些编程风格中,例如 C++,推荐使用 const 而不是 define,因为 const 提供了更多的类型安全性和更好的集成到语言中。

总的来说,两者都可以用来定义常量,但它们的适用场合并不完全相同。如果你需要更多的类型安全性或更细的作用域控制,使用 const 会更好。而如果你正在定义一个需要在多个文件或整个项目中使用的常量,define 可能是一个更好的选择。

注意

不要在#define语句的结尾处放一个分号;。这是一个常见的错误。

我们将在接下来的模块中学习更多关于预处理器指令的知识。

6. 输入及输出

6.1 输入 Input

C 支持多种方式获取用户输入。 如 getchar() 获取一个单字符的输入。

比如:

#include <stdio.h>

int main() {
    // 获取单个输入字符
    char a = getchar();

    printf("You entered: %c", a);

    return 0;
}

输入字符存储在变量 a 中。

gets() 函数用于读取输入的字符序列,也称为字符串。 一个字符串被存储在一个 char 数组中。 例如:

#include <stdio.h>

int main() {
    char a[100];

    // 获取字符串
    gets(a); 

    printf("You entered: %s", a);

    return 0;
}

在这里,我们将输入的内容存储在一个大小100字符的数组中。

scanf() 扫描符合格式指定符的输入。

例如:

#include <stdio.h>

int main() {
    int a;
    scanf("%d", &a);

    printf("You entered: %d", a);

    return 0;
}

变量名前的 & 符号是地址运算符。它给出了一个变量的地址,或在内存中的位置。这样做是因为 scanf 将一个输入值放在一个变量地址上。

  1. scanf:获取用户输入
  2. %d:指定用户输入的数据类型;
  3. 上面得到用户输入的值,存储在 a 变量中。但是,想要存储在 a 变量中,我们需要知道 a 的家庭住址「在这里,也就是 a 变量的物理地址」

想要获取用户输入的字符串,代码如下:

#include <stdio.h>

int main() {
    char inputString[100];
    printf("请输入一串字符:");
    scanf("%s", inputString);

    printf("You entered: %s", inputString);

    return 0;
}

Question 2

scanf("%s", inputString);为什么不需要 &?

在 C 语言中,当你需要使用 scanf() 函数读取一个整型、浮点型等基本数据类型的值时,你需要在变量名前添加 & 操作符,这是因为 scanf() 函数需要知道这个变量的内存地址,以便将用户的输入值存储在那里。这就是 & 操作符的作用——获取变量的内存地址。

然而,当你使用 scanf() 函数读取字符串时,情况就不同了。在 C 语言中,字符串其实是字符数组,而数组名本身就是一个指向数组首元素的指针,也就是说,它已经是一个地址了。因此,当你使用 scanf() 函数读取字符串时,你只需要直接传入数组名(即字符串名),而无需在前面添加 & 操作符。

所以,当你写 scanf("%s", inputString); 时,你实际上是在告诉 scanf() 函数:“请将输入的字符串存储在名为 inputString 的字符数组的起始位置(即 inputString 的地址)”。这也是为什么你不需要添加 & 操作符的原因。

练习: 输入两个整数并输出它们的和。

#include <stdio.h>

int main() {
    int a, b;
    printf("Enter two numbers:");
    scanf("%d %d", &a, &b);

    printf("\nSum: %d", a+b);

    return 0;
}

注意

scanf()一旦遇到空格就会停止读取,所以像 "Hello World "这样的文本对scanf()来说是两个独立的输入。

6.2 探究 scanf 获取字符串的特性

首先,我们需要明白 scanf() 是如何工作的。scanf() 是一个用于读取用户输入的函数。它通常和特定的格式说明符一起使用,例如:

  • %s(对应字符串)
  • %d(对应整数)
  • %f(对应浮点数)等等。

这些说明符告诉 scanf() 我们期待输入的数据类型。

特别地,当我们用 %s 格式说明符与 scanf() 一起使用时,它会读取连续的字符,直到遇到一个"空白"字符(比如空格、制表符或换行符)。因此,如果我们试图使用 scanf() 读取"Hello World"这样的带空格的字符串,那么 scanf() 只会读取"Hello",因为空格就在这之后。

这是一个代码示例,以及运行它的结果:

#include <stdio.h>

int main() {
    char str[20];
    printf("请输入一个字符串: ");
    scanf("%s", str);
    printf("你输入的字符串是: %s\n", str);
    return 0;
}

如果你在提示下输入"Hello World",输出将会是:

请输入一个字符串: Hello World
你输入的字符串是: Hello

如果你想读取含有空格的字符串,你应该使用fgets()函数,它可以读取整行(包括空格),直到遇到换行符或者达到给定的最大字符数。例如:

#include <stdio.h>

int main() {
    char str[20];
    printf("请输入一个字符串: ");
    fgets(str, 20, stdin); // 从标准输入(stdin)读取最多20个字符
    printf("你输入的字符串是: %s\n", str);
    return 0;
}

这次,如果你在提示下输入"Hello World",输出将会是:

请输入一个字符串: Hello World
你输入的字符串是: Hello World

注意:fgets()会读取换行符(如果在给定字符限制内),因此在输出中可能会包含换行符。如果你想移除这个换行符,你可以编写额外的代码来处理这个问题。

stdin 20个限制不能改么?

fgets() 函数的第二个参数并不是限制 stdin 的输入,而是你定义的缓冲区大小。这个参数应当等于你的字符数组的大小。如果你的数组大小为 20,那么 fgets() 函数将最多读取 19 个字符和一个 \0 结束符。这是因为在C语言中,字符串是以 \0 字符结束的。

这就意味着如果你想读取更长的字符串,你应该定义一个更大的字符数组。例如,如果你想读取最多200个字符,你可以这样做:

#include <stdio.h>

int main() {
    char str[200];
    printf("请输入一个字符串: ");
    fgets(str, 200, stdin);
    printf("你输入的字符串是: %s\n", str);
    return 0;
}

这个程序将允许用户输入最多199个字符(因为还需要一个字符空间来存放\0结束符)。如果用户输入超过199个字符,fgets()函数将只读取前199个字符。

fgets 中如果缓冲区的数字大于数组中的数字会怎么样?
解析

如果在 fgets() 函数中,你指定的缓冲区大小大于你实际定义的字符数组大小,那么可能会导致缓冲区溢出。这是一种非常危险的情况,因为它可能会覆盖掉你的程序其他部分的数据,或者导致程序崩溃。

例如,假设你有一个只能存储 10 个字符的数组,但是你试图从 stdin 中读取 100 个字符:

#include <stdio.h>

int main() {
    char str[10];
    printf("请输入一个字符串: ");
    fgets(str, 100, stdin); // 这里定义的缓冲区大小比数组的大小大,可能会导致缓冲区溢出
    printf("你输入的字符串是: %s\n", str);
    return 0;
}

这段代码是有问题的,因为当用户输入的字符串长度超过数组的长度时,fgets() 将会写入超过数组长度的数据,可能会破坏内存中的其他数据,导致未定义的行为。这可能会引发安全问题,因此必须避免。

总的来说,你总是需要确保你为 fgets() 提供的缓冲区大小不超过你的字符数组的实际大小,以防止缓冲区溢出。

填空,读取单个字符,并存储在字符变量c中。

char c = ___();
答案

getchar

6.3 scanf 是否需要 & 速查表

在 C 语言中,当使用 scanf 函数来获取用户输入时,我们经常需要使用取址符( & )来指定我们想要存放数据的变量的地址。这样,scanf 可以直接修改该内存位置的值。以下是一个基于数据类型的简单表格,显示了哪些类型需要取址符以及哪些不需要:

数据类型是否需要取址符 (&)示例
int需要scanf("%d", &num);
float需要scanf("%f", &f);
double需要scanf("%lf", &d);
char需要scanf(" %c", &c);
long需要scanf("%ld", &l);
short需要scanf("%hd", &s);
long long需要scanf("%lld", &ll);
unsigned int需要scanf("%u", &unum);
char[] (字符串数组)不需要scanf("%s", str);
char* (字符指针)不需要scanf("%s", strPtr);

注意:

  1. 字符串 (char[]char*) 在使用 scanf 时不需要取址符,因为数组名或字符指针本身就代表一个地址。
  2. 当读取 char 类型时,通常在格式字符串中放置一个空格(如" %c"),以跳过可能存在的换行符或空格。
  3. 在实际使用中,应确保为 scanf 提供足够大小的缓冲区以避免溢出,特别是当读取字符串时。
  4. 使用 scanf 读取其他复杂数据结构或自定义数据类型时,通常也需要使用取址符。

总的来说,基本的数据类型(如intfloatchar等)在使用scanf时都需要取址符。但字符串和指针不需要取址符,因为它们本身就是地址。

6.4 输出 Output

在前面的小节中,我们已经使用了 printf() 函数来输出内容。在本小节中,我们将介绍其他几个同于输出的函数。关于 printf 函数,我后面会专门讲一下。

putchar() 输出单一字符。 比如:

#include <stdio.h>

int main() {
  char a = getchar();

  printf("You entered: ");
  putchar(a);

  return 0;
}

输入存储在变量 a 中,并用 putchar(a) 输出字符变量 a

puts()函数用于将输出一个字符串。 一个字符串存储在一个 char 数组中。 例如:

#include <stdio.h>

int main() {
  char a[100];

  gets(a); 

  printf("You entered: ");
  puts(a); 

  return 0;
} 

在这里,我们将输入的内容存储在 100 个字符的数组中,并用 puts() 来输出该字符串。

scanf() 扫描符合格式指定符的输入。

例如:

#include <stdio.h>

int main() {
    int a;
    scanf("%d", &a);

    printf("You entered: %d", a);

    return 0;
}

变量名前的 & 符号是地址运算符。它给出了一个变量的地址,或在内存中的位置。需要这样做是因为 scanf 将一个输入值放在一个变量地址上

作为另一个例子,让我们输入整数并输出它们的总和:

#include <stdio.h>

int main() {
    int a, b;
    printf("Enter two numbers:");
    scanf("%d %d", &a, &b);

    printf("\nSum: %d", a+b);

    return 0;
}

填空,输出单字符变量c

char c = 's';
___(c);

6.5 printf 函数

C 语言中,输出内容到屏幕的基本函数是 printf() 函数,该函数声明在 stdio.h 头文件中。

在第一个"Hello World"程序中引入了 printf 函数。对这个函数的调用需要一个格式字符串,其中可以包括用于输出特殊字符的转义字符和由值替换的格式指定符

例如:

#include <stdio.h>

int main() {
    printf("The tree has %d apples.\n", 22);
    /* output: The tree has 22 apples. */

    printf("\"Hello World!\"\n");
    /* output: "Hello World!" */
}

现在,我们来学习如何使用它。

6.5.1 printf() 函数基本使用

首先,我们从最基础的 printf() 函数开始。下面是一个最简单的 C 程序,该程序使用 printf() 函数向控制台打印出一段文字。

#include <stdio.h>  // 引入stdio.h头文件,这个头文件中声明了printf()函数

int main() {  // main函数是C程序的入口
    printf("Hello, World!\n");  // 使用printf函数打印一段文字到控制台
    return 0;  // main函数结束,返回0
}

6.5.2 printf() 函数中的转义序列

有一些特殊的字符,我们不能直接在 printf() 函数中打印出来,例如换行符、制表符等。

转义字符以反斜线\开始。

这时候,我们需要用到转义序列。下面是一些常用的转义序列:

  • \n:换行符,打印完这个字符后,光标会移动到下一行的开始位置。
  • \t:制表符,打印完这个字符后,光标会移动到下一个制表位置。
  • \\:反斜杠,用来打印一个\字符。
  • \":双引号,用来打印一个"字符。
  • \b:退格
  • \':单引号
  • \":双引号
#include <stdio.h>

int main() {
    printf("Hello,\nWorld!\n");  // 使用\n来打印换行
    printf("Hello,\tWorld!\n");  // 使用\t来打印制表符
    printf("Hello,\\World!\n");  // 使用\\来打印反斜杠
    printf("Hello,\"World!\"\n");  // 使用\"来打印双引号
    return 0;
}

6.5.3 printf() 函数中的格式化输出

printf() 函数中,我们还可以进行格式化输出,这就涉及到了格式控制符。

格式指定符以百分号%开始,并由格式字符串后的相应参数取代。一个格式指定符可以包括几个选项,以及一个转换字符:

%[-][width].[precision]conversion character 
  • 可选的 - 指定了字符串中数据的左对齐;「没有 - 的,是右对齐」
  • 可选的 width (宽度)提供了数据的最小字符数;
  • 其中.将宽度 width 与精度 precision 分开;

可选的 precision (精度)给出了数字的小数位数。如果使用 s 作为转换字符,那么精度决定了要打印的字符数。

如有必要,转换字符将参数conversion character转换为指定类型,下面是一些常用的格式控制符:

  • %d:整型数据(十进制)
  • %c:字符
  • %s:字符串
  • %f:浮点数
  • %e:科学记数法
  • %x: 十六进制
demo1
#include <stdio.h>

int main() {
    int i = 10;
    char c = 'A';
    char s[] = "Hello, World!";
    float f = 3.14;
    
    printf("%d\n", i);  // 打印整型变量i
    printf("%c\n", c);  // 打印字符变量c
    printf("%s\n", s);  // 打印字符串s
    printf("%.2f\n", f);  // 打印浮点数f,保留2位小数
    
    return 0;
}

要打印%符号,在格式字符串中使用%%

这就是 C 语言中使用 printf() 函数进行输出的基本方法,通过 printf() 函数和格式控制符,我们可以将各种类型的数据按照我们想要的格式打印到控制台。

练习:

填空,格式化输出数字4.42

printf("%3.___f", 4.4289743);
字符串格式控制符

C语言中的 printf 函数支持多种格式控制符来打印各种数据类型。以下是最常用的格式控制符列表:

控制符描述
%d以十进制形式输出带符号整数
%i以十进制形式输出带符号整数(与 %d 相同)
%u以十进制形式输出无符号整数
%f输出单精度浮点数
%lf输出双精度浮点数
%e使用科学计数法格式输出单精度浮点数
%E使用科学计数法格式输出单精度浮点数(使用大写的 E
%g对于 %f%e,输出长度较短的一种
%G对于 %f%E,输出长度较短的一种
%x以十六进制形式输出无符号整数(小写字母)
%X以十六进制形式输出无符号整数(大写字母)
%o以八进制形式输出无符号整数
%s输出一个字符串
%c输出一个字符
%p输出指针的值
%n将到目前为止输出的字符数存入整数指针参数所指的位置
%%输出 % 符号

这些控制符可以与标志、宽度、精度和长度修饰符结合使用,以控制输出的格式。例如,%6.2f 表示输出的浮点数至少有6个字符宽,且小数点后有两位数字。

长度修饰符可以与某些格式控制符结合,以表示特定的大小或者长度的变量类型,例如:

长度修饰符描述
hh与整数格式控制符一起使用,表示 signed charunsigned char
h与整数格式控制符一起使用,表示 short int
l与整数格式控制符一起使用,表示 long int;与 %f, %e, %g 一起使用表示 double
ll与整数格式控制符一起使用,表示 long long int
L%f, %e, %g 一起使用,表示 long double
j与整数格式控制符一起使用,表示 intmax_tuintmax_t
z与整数格式控制符一起使用,表示 size_t
t与整数格式控制符一起使用,表示 ptrdiff_t

请注意,支持的长度修饰符可能会根据不同的编译器和平台而有所不同。

6.6 格式化输入

scanf() 函数用于将输入分配给变量。对这个函数的调用是根据格式指定符扫描输入,在必要时转换输入。

如果输入不能被转换,那么就不进行赋值。 scanf() 语句会等待输入,然后进行赋值。

#include <stdio.h>

int main() {
    int x;
    float num;
    char text[20];
    scanf("%d %f %s", &x, &num, text);
}

输入 10 22.5 abcd,然后按回车键,将 10 分配给 x22.5 分配给 numabcd 分配给 text。

注意,必须使用 & 来访问变量地址。字符串不需要 &,因为字符串的名字就像一个指针

格式指定符以百分号 % 开始,用于为控制字符串后的相应参数赋值。空白、制表符和换行符被忽略

一个格式指定符可以包括几个选项和一个转换字符:

%[*][max_field]conversion character
  • 可选的 * 将跳过输入字段。
  • 可选的 max_width 指定了输入字段的最大字符数。

如有必要,转换字符将参数 conversion character 转换为指定的类型,如:

  • d - 十进制
  • c - 字符
  • s - 字符串
  • f - 浮点数
  • x - 十六进制

例如:

demo
#include <stdio.h>

int main() {
    int x, y;
    char text[20];

    scanf("%2d %d %*f %5s", &x, &y, text);
    /* input: 1234  5.7  elephant */
    printf("%d  %d  %s", x, y, text);
    /* output: 12  34  eleph */

    return 0;
} 

每个程序都从读取用户输入开始,然后打印出用户输入的值。在这些例子中,'%' 后面的字符定义了输入或输出值的类型,而 '&' 用于获取变量的地址,以便 scanf 可以将读取的值存储在正确的位置。

详细解释上面的 demo 代码:

这个程序首先从用户输入获取数据,然后将数据赋值给指定的变量,最后打印这些变量的值。

以下是具体步骤的解释:

  1. 程序首先调用 scanf 函数从用户输入读取数据。格式字符串 "%2d %d %*f %5s" 定义了需要读取的数据类型和格式。这个字符串表示:

    • 读取一个最多两位的十进制整数并存储到变量 x 中(对应 "%2d"
    • 然后跳过一个或多个空格(对应 " "
    • 再读取一个十进制整数并存储到变量 y 中(对应 "%d"
    • 然后跳过一个或多个空格(对应 " "
    • 然后读取一个浮点数,但是忽略它不保存(对应 "%*f"
    • 然后跳过一个或多个空格(对应 " "
    • 最后读取一个最多五个字符的字符串并存储到 text 数组中(对应 "%5s")。
  2. 用户输入的数据是 "1234 5.7 elephant",按照上面的规则,程序将:

    • 读取 "12" 作为第一个整数(因为只读取两位),并将其存储到 x 中,所以 x 的值为 12
    • 然后读取 "34" 作为第二个整数,并将其存储到 y 中,所以 y 的值为 34
    • 然后读取 "5.7" 作为浮点数,但是忽略它不保存
    • 最后读取 "eleph" 作为字符串(因为只读取五个字符),并将其存储到 text 中,所以 text 的值为 "eleph"
  3. 然后,程序使用 printf 函数打印变量 x、y 和 text 的值。输出结果是 "12 34 eleph",这与我们前面解释的结果一致。

为什么第二个 %d 是读取前面的整数剩余的?

这是因为 scanf 函数的工作方式。scanf 会从左到右读取输入,然后根据格式字符串来解析这个输入。当 scanf 遇到一个空格或者换行符时,它会停止读取当前的字段,然后跳到下一个字段。

在你给出的代码中,格式字符串是 "%2d %d %*f %5s"。第一个 %2dscanf 读取一个最多两位的十进制整数。因此,当用户输入 "1234" 时,scanf 只会读取前两位,即 "12",并将它存储在变量 x 中。

然后 scanf 遇到了空格,它知道第一个字段(也就是前两位数)已经结束,所以它跳到下一个字段。下一个字段是 %d,这表示 scanf 应该读取一个十进制整数。因此,scanf 会继续从用户输入中读取,从刚才停止的地方开始,也就是 "34",并将它存储在变量 y 中。

这就是为什么第二个 %d 会读取前面的整数剩余的部分。如果你想让每个 %d 都只读取两位,你可以将格式字符串改为 "%2d %2d %*f %5s",这样 scanf 就会在读取每个整数的两位之后停止。

所以,scanf 其实就是一个“流”。

例如:

解析

scanf 获取用户输入,比如现在用户输入 11gtq aiyc 123

那么我们现在有一个字符串了:11gtq aiyc 123

  1. 此时我们有匹配的格式:%d %s,那么对应的匹配结果为:
符号内容
%d
%s
  1. 此时我们有匹配的格式:%d %s %d,那么对应的匹配结果为:
符号内容
%d
%s
%d

填空,使用scanf()输入两个整数变量:

int num1, num2;
scanf("%d %d", &__, ___num2);
答案

num1

&(取址符)

7. 代码注释

7.1 多行注释

注释是程序代码的解释信息,你可以在程序中添加注释,让代码更易阅读理解。编译器会忽略注释,所以它们对程序没有影响。

注释以斜线 /* 开始,以斜线 */ 结束,可以出现在代码的任何地方。 注释可以和语句在同一行,也可以跨越多行。 例如:

#include <stdio.h>

/* 简单的c语程序
 *  Version 1.0
 */
int main() {
    /* 输出字符串 */
    printf("Hello World!");
    return 0;
}

正如你所看到的,注释向读者澄清了程序的意图。使用注释来澄清代码段背后的目的和逻辑。

7.2 单行注释

C++ 引入了双斜线注释 // 作为注释单行的一种方式。大部分 C 语言编译器也支持单行注释。 例如:

#include <stdio.h>

int main() {
    int x = 42; // 声明int变量x
    
    //%d 占位符,此处代表x
    printf("%d", x);
    
    return 0;
}

给代码添加注释是良好的编程实践。它有助于你和他人清楚地了解代码。

8. 算术运算符

C 支持的 算术运算符+(加法)、-(减法)、*(乘法)、/(除法)和 %(模除法)。

运算符经常用来组成一个算术表达式,如10+5,在这种情况下,它包含两个操作数加法运算符。

算术表达式经常用于赋值语句中。 例如:

#include <stdio.h>

int main() {
    int length = 10;
    int width = 5;
    int area;

    area = length * width;
    printf("%d \n", area);  /* 50 */

    return 0;
}

8.1 除法

C 语言有两个除法运算符: /%。 根据操作数的数据类型,除法 / 运算符有不同的表现。

当两个操作数都是 int 数据类型时,为整数除法,也称为截断除法,去掉任何余数,结果是一个整数。

当一个或两个操作数都是实数(floatdouble )时,结果是一个实数。

% 运算符只返回整数除法的余数。它对许多算法很有用,如欧几里得算法求最大公约数。但模数除法不能在浮点数上执行。

下面的例子演示了除法:

#include <stdio.h>

int main() {
    int i1 = 10;
    int i2 = 3;
    int quotient, remainder;
    float f1 = 4.2;
    float f2 = 2.5;
    float result;

    quotient = i1 / i2;  // 3
    remainder = i1 % i2;  // 1
    result = f1 / f2;  // 1.68

    printf("%d \n", quotient);
    printf("%d \n", remainder);
    printf("%f \n", result);

    return 0;
}

下面代码,result最终等于多少?

int a = 12;
int b = 5;
int result = a % b;
练习

题目: 两位数操作练习

要求:

编写一个 C 语言程序,执行以下操作:

  1. 提示用户输入一个两位数的整数。
  2. 确保用户输入的是一个有效的两位数。否则,提示用户重新输入。
  3. 计算并显示该整数的十位数和个位数的和。
  4. 翻转整数,并显示翻转后的结果。

示例:

请输入一个两位数的整数:123
输入错误!请输入一个两位数。
请输入一个两位数的整数:89
十位数和个位数的和为: 17
翻转后的整数为: 98

解答:

#include <stdio.h>

int main() {
    int number, tens, ones, sum, reversed;

    while(1) {
        // 获取用户输入
        printf("请输入一个两位数的整数:");
        scanf("%d", &number);

        // 检查输入是否为两位数
        if (number < 10 || number > 99) {
            printf("输入错误!请输入一个两位数。\n");
            continue;
        } else {
            break;
        }
    }

    // 计算十位数和个位数
    tens = number / 10;
    ones = number % 10;

    // 计算它们的和
    sum = tens + ones;
    printf("十位数和个位数的和为: %d\n", sum);

    // 翻转整数
    reversed = ones * 10 + tens;
    printf("翻转后的整数为: %d\n", reversed);

    return 0;
}

通过上述代码,用户可以多次尝试输入,直到输入一个有效的两位数为止。

8.2 运算符优先级

C 语言根据运算符的优先级来计算算术表达式。

+- 的优先级相同,*/% 的优先级也相同。

首先按照从左到右的顺序执行 */%,然后是 +- 。 你可以通过使用圆括号 () 来改变计算的顺序,表示哪些运算要先执行。

例如,5+3*2 的结果是 11,而 (5+3)*2 的结果是16。

例如:

#include <stdio.h>

int main() {
    int a = 6;
    int b = 4;
    int c = 2;
    int result;
    result = a - b + c;  // 4
    printf("%d \n", result);
    result = a + b / c;  // 8
    printf("%d \n", result);
    result = (a + b) / c;  // 5
    printf("%d \n", result);

    return 0;
}

注意

C 在执行计算算术表达式时,对于顺序无关的运算可能不是严格的从左到右计算。例如,x*y*z 可能被视为(x * y) * zx * (y * z)。如果顺序很重要,请将表达式分成不同的语句。

练习: 填空,变量 x 减去 y,再相加 z 并将结果赋值给 result

int x = 6;
int y = 4;
int z = 2;

int res = x _____ y _______ z;
C 语言运算符优先级

C 语言的运算符优先级决定了表达式中多个运算符的运算顺序。以下是 C 语言运算符的优先级列表,从最高到最低:

  1. 后缀

    • (): 函数调用
    • []: 数组下标
    • .: 结构体成员选择
    • ->: 通过指针选择结构体/联合体成员
    • ++: 后缀递增
    • --: 后缀递减
  2. 前缀

    • ++: 前缀递增
    • --: 前缀递减
    • +: 正号
    • -: 负号
    • !: 逻辑非
    • ~: 位非
    • *: 解引用
    • &: 取址
    • sizeof: 获取大小
    • _Alignof: 对齐要求 (C11起)
    • (类型):强制类型转换
  3. 乘除

    • *: 乘
    • /: 除
    • %: 取余
  4. 加减

    • +: 加
    • -: 减
  5. 位移

    • <<: 左移
    • >>: 右移
  6. 关系

    • <: 小于
    • <=: 小于等于
    • >: 大于
    • >=: 大于等于
  7. 相等

    • ==: 等于
    • !=: 不等于
  8. 位与

    • &
  9. 位异或

    • ^
  10. 位或

    • |
  11. 逻辑与

    • &&
  12. 逻辑或

    • ||
  13. 条件

    • ? : (三元运算符)
  14. 赋值

    • =: 赋值
    • +=: 加并赋值
    • -=: 减并赋值
    • *=: 乘并赋值
    • /=: 除并赋值
    • %=: 取余并赋值
    • <<=: 左移并赋值
    • >>=: 右移并赋值
    • &=: 位与并赋值
    • ^=: 位异或并赋值
    • |=: 位或并赋值
  15. 逗号

    • , (逗号运算符)

在一个复杂的表达式中,你可以使用括号 () 来改变运算顺序,明确优先级。

8.3 类型转换

当一个算术表达式包含不同数据类型的操作数时,它们会在一个称为类型转换的过程中自动进行必要的转换。

例如,在一个同时涉及浮点数 float 和整数 int 的计算中,编译器将把整数值转换为浮点数。

在下面的程序中,变量 increase 被自动转换为浮点数

#include <stdio.h>

int main() {
    float price = 6.50;
    int increase = 2;
    float new_price;

    new_price = price + increase;
    printf("New price is %4.2f", new_price);
    /* Output: New price is 8.50 */

    return 0;
}

请注意,格式指定符包括 4.2,表示浮点数将打印在一个至少 4 个字符宽的空间中,并有 2 个小数位。

C 语言精度顺序

在 C 语言中,当两个不同的数据类型进行运算时,C 语言会进行自动类型转换,以确保数据不会因为类型不匹配而发生错误。这种转换通常是根据类型的“精度”来进行的。以下是 C 语言中基本数据类型的精度顺序,从最低到最高:

  1. charunsigned char
  2. short intunsigned short int
  3. intunsigned int
  4. long intunsigned long int
  5. long long intunsigned long long int
  6. float
  7. double
  8. long double

当两个不同类型的值进行运算时,C 语言会自动将精度较低的类型转换为精度较高的类型,然后再进行运算。

例如,如果你有一个 int 值和一个 double 值并将它们相加,C 语言会首先将 int 值转换为 double,然后再进行加法运算。

注意:这种自动类型转换可能会引起某些不预期的问题,尤其是当涉及到有符号和无符号类型时。因此,编程时最好明确数据类型,确保类型转换的准确性。

当你想把表达式的结果强制转换成不同的类型时,你可以通过类型转换进行显式的转换,如下代码:

#include <stdio.h>

int main() {
    float average;
    int total = 23;
    int count = 4;
    // total 强制转换为float 类型
    average = (float) total / count;
    
    printf("%4.2f", average);

    return 0;
}

如果不进行类型转换,变量 average 将赋值为 5。 显示的进行类型转换,即使编译器可以做自动类型转换,也认为是好的编程风格。

填空,显示的将除法结果转换为float类型:

average = (___) total / count;

8.4 赋值运算符

赋值语句首先评估等号( = )右边的表达式,然后将该值赋给 = 左边的变量。 这使得在赋值语句的两边使用同一个变量是有可能的,且在编程中经常使用该特性。

例如:

int x = 3;
x = x + 1;  /* x 现在为 4 */

为了缩短此类型的赋值语句,C 语言提供了 += 赋值运算符。上面的语句可以写成:

x += 1;  /* x = x + 1 */

许多 C 语言的运算符都有一个相应的赋值运算符。下面的程序演示了算术赋值运算符:

int x = 2;

x += 1;  // 3
x -= 1;  // 2
x *= 3;  // 6
x /= 2;  // 
x %= 2;  // 1
x += 3 * 2;  // 7

仔细看一下最后一条赋值语句。右边的整个表达式被计算,然后和 x 相加,再赋值给 x。等价于 x = x + (3 * 2)

填空,将 int 类型变量num2相加到变量num1中:

int num1 = 8;
int num2 = 42;
num1___num2;

8.5 自增及自减运算

给一个变量加1可以用自增运算符 ++ 来完成。同样地,自减运算符 -- 用于从一个变量中减去1。 例如:

z--;  /* z 减去 1 */
y++; /* y 增加 1 */

自增和自减运算符分为前缀(在变量名之前)或后缀(在变量名之后)。在赋值语句中,前缀或后缀 自增、自减结果不一样,如下面的例子:

#include <stdio.h>

int main() {
    int x, y, z;
    
    z = 3;
    x = z--;  /* z先赋值给x,x=3,之后z 自减,z=2*/
    printf("x=%d \n", x);
    
    y = 3;
    x = ++y;  /* y先自增1,y=4, 之后y赋值给x,x=4 */
    
    printf("x=%d \n", x);
    
    printf("y=%d \n", y);

    return 0;
}
  • 前缀形式是先增加、减少变量,然后在赋值语句中使用它。
  • 后缀形式首先使用变量的值,然后再进行增减。

下面代码,变量 x 最终等于多少?

int x = 8;
int y = 7;
x++;
x+= y--;

8.6 对比学习

1. x--;

x-- 代码
#include <stdio.h>

int main() {
    int x = 9;
    int y;
    y = 2 * x--;
    printf("%d \n", x);
    printf("%d", y);
    return 0;
}

// ---output---
8
18

2. --x;

--x 代码
#include <stdio.h>

int main() {
    int x = 9;
    int y;
    y = 2 * --x;
    printf("%d \n", x);
    printf("%d", y);
    return 0;
}

// ---output---
8
16

3. x++;

x++ 代码
#include <stdio.h>

int main() {
    int x = 9;
    int y;
    y = 2 * x++;
    printf("%d \n", x);
    printf("%d", y);
    return 0;
}

// ---output---
10 
18

4. ++x;

++x 代码
#include <stdio.h>

int main() {
    int x = 9;
    int y;
    y = 2 * ++x;
    printf("%d \n", x);
    printf("%d", y);
    return 0;
}

// ---output---
10 
20

9. 练习

  1. C 语言执行的主入口是?

    A. 代码第一行

    B. main() 函数

    C. <stdio.h> 头文件

  2. 填空,使用 printf 输出文本"I love C":

    ___("I love C")___
    
  3. 下面哪一项是 c 语言单行注释?

    A. ## 单行注释

    B. ** 单行注释

    C. // 单行注释

  4. 填空,声明一个变量 sum,并将变量 ab 相加赋值给 sum:

    int___ = a ___ b;
    
  5. 填空,输出变量 v

    ___include <stdio.h>
    int main() {
        int v = 42135;
        printf("%d", ___);
        return 0;
    }
    
公众号:AI悦创【二维码】

AI悦创·编程一对一

AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web、Sql」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh

C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度