开始学习第七章其余部分。


7.3.4 转义序列

正如在前面示例中见到的那样,字符常量通常是用单引号括起来的单个字符。然而,一些特殊符号(比如换行符)是无法采用上述方式书写的,因为它们不可见(非打印字符),或者无法从键盘输入。因此,为了使程序可以处理字符集中的每一个字符,C语言提供了一种特殊的表示法——转义序列。

escape翻译成“转义”,而计算机领域的"escape"特指符号系统的规则转换

转义序列共有两种:字符转义序列 数字转义序列 。在3.1节已经见过了一部分字符转义序列,下表给出了完整的字符转义序列集合。

字符转义序列

名称转义序列
警报(响铃)符\a
回退符\b
换页符\f
换行符\n
回车符\r
水平制表符\t
垂直制表符\v
反斜杠        \\
问号        \?
单引号\'
双引号\"

转义序列\a 、\b 、\f 、\r 、\t 和\v 表示常用的ASCII控制字符, 转义序列\n 表示ASCII码的回行符,转义序列\\ 允许字符常量或字符串包含字符\ ,转义序列\' 允许字符常量包含字符' ,而转义序列\" 则允许字符串包含字符" , 转义序列\? 很少使用。

字符转义序列使用起来很容易,但是它们有一个问题:转义序列列表没有包含所有无法打印的ASCII字符,只包含了最常用的字符。字符转义序列也无法用于表示基本的128个ASCII字符以外的字符。数字转义序列可以表示任何字符,所以它可以解决上述问题。

为了把特殊字符书写成数字转义序列,首先需要在类似附录E那样的表中查找字符的八进制或十六进制值。例如,某个ASCII码转义字符(十进制值为27)对应的八进制值为33,对应的十六进制值为1B。上述八进制或十六进制码可以用来书写转义序列。

八进制转义序列 由字符\ 和跟随其后的一个最多含有三位数字的八进制数组成

(此数必须表示为无符号字符,所以最大值通常是八进制的377。)例如,可以将转义字符写成\33 或\033。跟八进制常量不同,转义序列中的八进制数不一定要用0开头。

十六进制转义序列 由\x 和跟随其后的一个十六进制数组成

虽然标准C对于十六进制数的位数没有限制,但其必须表示成无符号字符(因此,如果字符长度是8位,那么十六进制数的值不能超过FF )。若采用这种表示法,可以把转义字符写成\x1b 或\x1B的形式。字符x 必须小写,但是十六进制的数字(例如b )不限大小写。

作为字符常量使用时,转义序列必须用一对单引号括起来。例如,表示转义字符的常量可以写成'\33' (或'\x1b' )的形式。转义序列可能有点隐晦,所以采用#define 的方式给它们命名通常是个不错的主意:

#define ESC '\33' /* ASCII escape character */

正如3.1节看到的那样,转义序列也可以嵌在字符串中使用。

转义序列不是唯一一种用于表示字符的特殊表示法。三字符序列提供了一种表示字符# 、[ 、\ 、] 、^ 、{ 、| 、}和~ 的方法,这些字符在一些国家的键盘上是打不出来的。 C99增加了通用字符名。通用字符名跟转义序列相似,不同之处在于通用字符名可以用在标识符中。

7.3.5 字符处理函数

前面已经讲过如何使用if 语句把小写字母转换成大写字母:

if ('a' <= ch && ch <= 'z')ch = ch -'a' + 'A';

但是这不是最好的方法。一种更快捷且更易于移植的转换方法是调用C语言的toupper 库函数:

ch = toupper(ch); /* converts ch to upper case */

toupper 函数在被调用时检测参数(本例中为ch )是否是小写字母。如果是,它会把参数转换成相应的大写字母;否则, toupper函数会返回参数的值。上面的例子采用赋值运算符把toupper 函数返回的值存储在变量ch 中。当然也可以同样简单地进行其他的处理,比如存储到另一个变量中,或用if 语句进行测试:

if (toupper(ch) == 'A') ...

调用toupper 函数的程序需要在顶部放置下面这条#include 指令:

#include <ctype.h>

toupper 函数不是在C函数库中唯一实用的字符处理函数。后续会描述全部字符处理函数,并且给出使用示例。

7.3.6 用scanf 和printf 读/写字符

转换说明%c 允许scanf 函数和printf 函数对单个字符进行读/写操作:

char ch;
scanf("%c", &ch); /* reads a single character */
printf("%c", ch); /* writes a single character */

在读入字符前,scanf 函数不会跳过空白字符。如果下一个未读字符是空格,那么在前面的例子中,scanf 函数返回后变量ch 将包含一个空格。为了强制scanf 函数在读入字符前跳过空白字符,需要在格式串中的转换说明%c 前面加上一个空格:

scanf(" %c", &ch); /* skips white space, then reads ch */

回顾3.2节的内容,scanf 格式串中的空白意味着“跳过零个或多个空白字符”。因为通常情况下scanf 函数不会跳过空白,所以它很容易检查到输入行的结尾:检查刚读入的字符是否为换行符。例如,下面的循环将读入并且忽略掉当前输入行中剩下的所有字符:

do {scanf("%c", &ch);
} while (ch != '\n');

下次调用scanf 函数时,将读入下一输入行中的第一个字符。

7.3.7 用getchar 和putchar 读/写字符

C语言还提供了另外一些读/写单个字符的方法。特别是, 可以使用getchar 函数和putchar 函数来取代scanf 函数和printf 函数。putchar 函数用于写单个字符:

putchar(ch);


每次调用getchar 函数时,它会读入一个字符并将其返回。为了保存这个字符,必须使用赋值操作将其存储到变量中:

ch = getchar(); /* reads a character and stores it in ch */

事实上,getchar 函数返回的是一个int 类型的值而不是char 类型的值(原因将在后续章节中讨论)。因此,如果一个变量用于存储getchar 函数读取的字符,其类型设置为int 而不是char 也没啥好奇怪的。和scanf 函数一样,getchar 函数也不会在读取时跳过空白字符

执行程序时,使用getchar 函数和putchar 函数(胜于scanf 函数和printf 函数)可以节约时间。getchar 函数和putchar 函数执行速度快有两个原因。第一个原因是,这两个函数比scanf 函数和
printf 函数简单得多,因为scanf 函数和printf 函数是设计用来按不同的格式读/写多种不同类型数据的。第二个原因是,为了额外的速度提升,通常getchar 函数和putchar 函数是作为宏来实现的

getchar 函数还有一个优于scanf 函数的地方:因为返回的是读入的字符,所以getchar 函数可以应用在多种不同的C语言惯用法中,包括用在搜索字符或跳过所有出现的同一字符的循环中。思考下面这个scanf 函数循环,前面我们曾用它来跳过输入行的剩余部分:

do {scanf("%c", &ch);
} while (ch != '\n');

用getchar 函数重写上述循环,得到下面的代码:

do {ch = getchar();
} while (ch != '\n');

把getchar 函数调用移到控制表达式中可以精简循环

while ((ch = getchar()) != '\n');

这个循环读入一个字符,把它存储在变量ch 中,然后测试变量ch 是否不是换行符。如果测试结果为真,那么执行循环体(循环体实际为空),接着再次测试循环条件,从而引发读入新的字符。实际上我们并不需要变量ch ,可以把getchar 函数的返回值与换行符进行比较:

[惯用法]

while (getchar() != '\n'); /* skips rest of line */

这个循环是非常著名的C语言惯用法,虽然这种用法的含义是十分隐晦的,但是值得学习

getchar 函数对于搜索字符的循环和跳过字符的循环都很有用。思考下面这个利用getchar 函数跳过不定数量的空格字符的语句

[惯用法]

while ((ch = getchar()) == ' '); /* skips blanks */

当循环终止时,变量ch 将包含getchar 函数遇到的第一个非空白字符。

注意:如果在同一个程序中混合使用getchar 函数和scanf 函数,请一定要注意。scanf 函数倾向于遗留下它“扫视”过但未读取的字符(包括换行符)。思考一下,如果试图先读入数再读入字符的话,下面的程序段会发生什么:

printf("Enter an integer: ");
scanf("%d", &i);
printf("Enter a command: ");
command = getchar();

在读入i 的同时,scanf 函数调用将会留下没有消耗掉的任意字符,包括(但不限于)换行符。getchar 函数随后将取回第一个剩余字符但这不是我们所希望的结果

程序014 确定消息的长度

为了说明字符的读取方式,下面编写一个程序来计算消息的长度。在用户输入消息后,程序显示长度:

Enter a message: Brevity is the soul of wit.
Your message was 27 character(s) long.

消息的长度包括空格和标点符号,但是不包含消息结尾的换行符。程序需要采用循环结构来实现读入字符和计数器自增操作,循环在遇到换行符时立刻终止。我们既可以采用scanf 函数也可以采用getchar 函数读取字符,但大多数C程序员愿意采用getchar 函数。采用简明的while 循环书写的程序如下:

length.c

/* Determines the length of a message */
#include <stdio.h>
int main(void)
{char ch;int len = 0;printf("Enter a message: ");ch = getchar();while (ch != '\n') {len++;ch = getchar();}printf("Your message was %d character(s) long.\n", len);return 0;
}

回顾有关while 循环和getchar 函数惯用法的讨论,我们发现程序可以缩短成如下形式:

length2.c

/* Determines the length of a message */
#include <stdio.h>
int main(void)
{int len = 0;printf("Enter a message: ");while (getchar() != '\n')len++;printf("Your message was %d character(s) long.\n", len);return 0;
}

7.4 类型转换

在执行算术运算时,计算机比C语言的限制更多。为了让计算机执行算术运算,通常要求操作数有相同的大小(即位的数量相同),并且要求存储的方式也相同。计算机可能可以直接将两个16位整数相加,但是不能直接将16位整数和32位整数相加,也不能直接将32位整数和32位浮点数相加。

C语言则允许在表达式中混合使用基本类型。在单个表达式中可以组合整数、浮点数,甚至是字符。当然,在这种情况下C编译器可能需要生成一些指令将某些操作数转换成不同类型,使得硬件可以对表达式进行计算。例如,如果对16位short 型数和32位int 型数进行加法操作,那么编译器将安排把16位short 型值转换成32位值。如果是int型数据和float 型数据进行加法操作,那么编译器将安排把int 型值转换成为float 格式。这个转换过程稍微复杂一些,因为int 型值和float 型值的存储方式不同。

因为编译器可以自动处理这些转换而无需程序员介入,所以这类转换称为隐式转换 。C语言还允许程序员使用强制运算符执行显式转换 。先讨论隐式转换,显式转换推迟到本节的最后进行。遗憾的是,执行隐式转换的规则有些复杂,主要是因为C语言有大量不同的算术类型。

当发生下列情况时会进行隐式转换。当算术表达式或逻辑表达式中操作数的类型不相同时。(C语言执行所谓的常用算术转换 。)
① 当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时。

② 当函数调用中的实参类型与其对应的形参类型不匹配时。

③ 当return 语句中表达式的类型和函数返回值的类型不匹配时。

这里将讨论前两种情况,其他情况将留到第9章进行介绍。

7.4.1 常用算术转换

常用算术转换可用于大多数二元运算符(包括算术运算符、关系运算符和判等运算符)的操作数。例如,假设变量f 为float 类型,而变量i 为int 类型。

常用算术转换将会应用在表达式f + i 的操作数上,因为两者的类型不同。显然把变量i 转换成float 类型(匹配变量f 的类型)比把变量f 转换成int 类型(匹配变量i 的类型)更安全。

整数始终可以转换成为float 类型;可能会发生的最糟糕的事是精度会有少量损失。相反,把浮点数转换成为int 类型,将有小数部分的损失;更糟糕的是,如果原始数大于最大可能的整数或者小于最小的整数,那么将会得到一个完全没有意义的结果。


常用算术转换的策略是把操作数转换成可以安全地适用于两个数值的“最狭小的”数据类型

(粗略地说,若某种类型要求的存储字节比另一种类型少,则这种类型就比另一种类型更狭小。)

为了统一操作数的类型,通常可以将相对较狭小类型的操作数转换成另一个操作数的类型来实现(这就是所谓的提升 )。 最常用的提升是整值提升 ,它把字符或短整数转换成int 类型(或者某些情况下是unsigned int 类型)。

执行常用算术转换的规则可以划分成两种情况。

任一操作数的类型是浮点类型的情况 。按照下图将类型较狭小的操作数进行提升:

float -> double -> long double

也就是说,如果一个操作数的类型为long double ,那么把另一个操作数的类型转换成long double 类型。否则,如果一个操作数的类型为double 类型,那么把另一个操作数转化成double 类型。否则,如果一个操作数的类型是float 类型,那么把另一个操作数转换成float 类型。

注意,这些规则涵盖了混合整数和浮点类型的情况。例如,如果一个操作数的类型是long int 类型,并且另一个操作数的类型是double 类型,那么把long int 类型的操作数转换成double 类型。

两个操作数的类型都不是浮点类型的情况 。首先对两个操作数进行整值提升(保证没有一个操作数是字符类型或短整型)。然后按照下图对类型较狭小的操作数进行提升:

int ->unsigned int -> long int -> unsigned long int


有一种特殊情况,只有在long int 类型和unsigned int 类型长度(比如32位)相同时才会发生。在这类情况下,如果一个操作数的类型是long int ,而另一个的类型是unsigned int,那么两个操作数都会转换成unsigned long int 类型

注意:

当把有符号操作数和无符号操作数组合时,把有符号操作数“转换”成无符号的值。转换过程中需要加上或者减去的倍数,其中是无符号类型能表示的最大值。这条规则可能会导致某些隐蔽的编程错误

假设int 类型的变量 i 的值为-10,而unsigned int 类型的变量u 的值为10。如果用 < 运算符比较变量 i 和变量 u ,那么期望的结果应该是1(真)。但是,在比较前,变量 i 转换成为unsigned int 类型。因为负数不能被表示成无符号整数,所以转换后的值将不再为-10,而是加上4 294 967 296的结果(假定4 294 967 295是最大的无符号整数),即4 294 967 286。因而i < u 比较的结果将为0。有些编译器会在程序试图比较有符号数与无符号数时给出一条类似“comparison between signedand unsigned”的警告消息。

由于此类陷阱的存在,所以最好尽量避免使用无符号整数,特别是不要把它和有符号整数混合使用

下面的例子显示了常用算术转换的实际执行情况:

char c;
short int s;
int i;
unsigned int u;
long int l;
unsigned long int ul;
float f;
double d;
long double ld;
i = i + c; /* c is converted to int */
i = i + s; /* s is converted to int */
u = u + i; /* i is converted to unsigned int */
l = l + u; /* u is converted to long int */
ul = ul + l; /* l is converted to unsigned long int */
f = f + ul; /* ul is converted to float */
d = d + f; /* f is converted to double */
ld = ld + d; /* d is converted to long double */

7.4.2 赋值过程中的转换

常用算术转换不适用于赋值运算。C语言会遵循另一条简单的转换规则,那就是把赋值运算右边的表达式转换成左边变量的类型。如果变量的类型至少和表达式类型一样“宽”,那么这种转换将没有任何障碍。例如:

char c;
int i;
float f;
double d;
i = c; /* c is converted to int */
f = i; /* i is converted to float */
d = f; /* f is converted to double */

其他情况下是有问题的。把浮点数赋值给整型变量会丢掉该数的小数部分:

int i;
i = 842.97; /* i is now 842 */
i = -842.97; /* i is now -842 */

此外,把某种类型的值赋给类型更狭小的变量时, 如果该值在变量类型范围之外,那么将会得到无意义的结果(甚至更糟)。

c = 10000; /*** WRONG ***/
i = 1.0e20; /*** WRONG ***/
f = 1.0e100; /*** WRONG ***/

这类赋值可能会导致编译器或lint之类的工具发出警告。

如果浮点常量被赋值给float 型变量时,一个很好的方法是在浮点常量尾部加上后辍f ,我们从第2章开始就一直是这么做的:

f = 3.14159f;

如果没有后辍,常量3.14159 将是double 类型,可能会引起警告消息。

7.4.3 C99中的隐式转换

C99中的隐式转换和C89中的隐式转换略有不同,这主要是因为C99增加了一些类型(_Bool 、long long 类型、扩展的整数类型和复数类型)。

为了定义转换规则,C99允许每个整数类型具有“整数转换等级”。下面按从最高级到最低级的顺序排列。

(1) long long int 、unsigned long long int
(2) long int 、unsigned long int
(3) int 、unsigned int
(4) short int 、unsigned short int
(5) char 、signed char 、unsigned char
(6) _Bool

简单起见,这里忽略了扩展的整数类型和枚举类型。

C99用整数提升取代了C89中的整值提升,可以将任何等级低于int 和unsigned int 的类型转换为int (只要该类型的所有值都可以用int 类型表示)或unsigned int 。

与C89一样,C99中执行常用算术转换的规则可以划分为两种情况。

任一操作数的类型是浮点类型的情况 。

只要两个操作数都不是复数型,规则与前面一样。

两个操作数的类型都不是浮点类型的情况 。

首先对两个操作数进行整数提升。如果这时两个操作数的类型相同,过程结束。否则,依次尝试下面的规则,一旦遇到可应用的规则就不再考虑别的规则:
如果两个操作数都是有符号型或者都是无符号型,将整数转换等级较低的操作数转换为等级较高的操作数的类型;

如果无符号操作数的等级高于或等于有符号操作数的等级,将有符号操作数转换为无符号操作数的类型。

如果有符号操作数类型可以表示无符号操作数类型的所有值,将无符号操作数转换为有符号操作数的类型。

否则,将两个操作数都转换为与有符号操作数的类型相对应的无符号类型。
另外,所有算术类型都可以转换为_Bool 类型。如果原始值为0则转换结果为0,否则结果为1。

7.4.4 强制类型转换

虽然C语言的隐式转换使用起来非常方便,但我们有些时候还需要从更大程度上控制类型转换。基于这种原因,C语言提供了强制类型转换。强制类型转换表达式的格式如下:

[强制转换表达式]         (类型名) 表达式

这里的 类型名 表示的是表达式应该转换成的类型。

下面的例子显示了使用强制类型转换表达式计算float类型值小数部分的方法:

float f, frac_part;
frac_part = f - (int) f;

强制类型转换表达式(int) f 表示把f 的值转换成int 类型后的结果。C语言的常用算术转换则要求在进行减法运算前把(int)f 转换回float 类型。f 和(int)f 的不同之处就在于f 的小数部分,这部分在强制类型转换时被丢掉了。

强制类型转换表达式可以用于显示那些肯定会发生的类型转换:

i = (int) f; /* f is converted to int *

它也可以用来控制编译器并且强制它进行我们需要的转换。思考下面的例子:

float quotient;
int dividend, divisor;
quotient = dividend / divisor;

正如现在写的那样,除法的结果是一个整数,在把结果存储在quotient 变量中之前,要把结果转换成float 格式。但是,为了得到更精确的结果,可能需要在除法执行之前把dividend 和divisor 的类型转换成float 格式的。强制类型转换表达式可以完成这一点:

quotient = (float) dividend / divisor;

变量divisor 不需要进行强制类型转换,因为把变量dividend 强制转换成float 类型会迫使编译器把divisor 也转换成float 类型。
顺便提一下,C语言把(类型名) 视为一元运算符。一元运算符的优先级高于二元运算符,所以编译器会把表达式:

(float) dividend / divisor

解释为

((float) dividend) / divisor

如果感觉有点混淆,那么注意还有其他方法可以实现同样的效果:

quotient = dividend / (float) divisor;

或者

quotient = (float) dividend / (float) divisor;

有些时候,需要使用强制类型转换来避免溢出。思考下面这个例子:

long i;
int j = 1000;
i = j * j; /* overflow may occur */

乍看之下,这条语句没有问题。表达式j * j 的值是1 000 000,并且变量 i 是long int 类型的,所以i 应该能很容易地存储这种大小的值,不是吗?问题是,当两个int 类型值相乘时,结果也应该是int 类型的,但是j * j 的结果太大,以致在某些机器上无法表示成int 型,从而导致溢出。幸运的是,可以使用强制类型转换避免这种问题的发生:

i = (long) j * j;

因为强制运算符的优先级高于* ,所以第一个变量j 会被转换成long int 类型,同时也迫使第二个j 进行转换。注意,语句

i = (long) (j * j); /*** WRONG ***/

是不对的,因为溢出在强制类型转换之前就已经发生了。

7.5 类型定义

5.2节中,我们使用#define 指令创建了一个宏,可以用来定义布尔型数据:

#define BOOL int

但是, 一个更好的设置布尔类型的方法是利用所谓的类型定义的特性:

typedef int Bool;

注意,所定义的类型的名字放在最后 。还要注意,我们使用首字母大写的单词Bool 。将类型名的首字母大写不是必须的,只是一些C语言程序员的习惯。
采用typedef 定义Bool 会导致编译器在它所识别的类型名列表中加入Bool

现在,Bool 类型可以和内置的类型名一样用于变量声明、强制类型转换表达式和其他地方了。例如,可以使用Bool 声明变量:

Bool flag; /* same as int flag; */

编译器将会把Bool 类型看成是int 类型的同义词;因此,变量flag 实际就是一个普通的int 类型变量。

7.5.1 类型定义的优点

类型定义使程序更加易于理解(假定程序员是仔细选择了有意义的类型名)。例如,假设变量cash_in 和变量cash_out 将用于存储美元数量。把Dollars 声明成

typedef float Dollars;

并且随后写出

Dollars cash_in, cash_out;

这样的写法比下面的写法更有实际意义:

float cash_in, cash_out;

类型定义还可以使程序更容易修改。如果稍后决定Dollars 实际应该定义为double 类型的,那么只需要改变类型定义就足够了:

typedef double Dollars;

Dollars 变量的声明不需要进行改变。如果不使用类型定义,则需要找到所有用于存储美元数量的float 类型变量(这显然不是件容易的工作)并且改变它们的声明。

7.5.2 类型定义和可移植性

类型定义是编写可移植程序的一种重要工具。程序从一台计算机移动到另一台计算机可能引发的问题之一就是不同计算机上的类型取值范围可能不同。如果i 是int 类型的变量,那么赋值语句

i = 100000;

在使用32位整数的机器上是没问题的,但是在使用16位整数的机器上就会出错。

可移植性技巧 为了更大的可移植性,可以考虑使用typedef定义新的整数类型名

假设编写的程序需要用变量来存储产品数量, 取值范围在0~50 000。为此可以使用long int 类型的变量(因为这样保证可以存储至少在2 147 483 647以内的数),但是用户更愿意使用int 类型的变量,因为算术运算时int 类型值比long int 类型值运算速度快;同时,int 类型变量占用的空间较少。我们可以定义自己的“数量”类型,而避免使用int 类型声明数量变量:

typedef int Quantity;

并且使用这种类型来声明变量:

Quantity q;

当把程序转到使用较短整数的机器上时,需要改变Quantity 的定义:

typedef long Quantity

可惜的是,这种技术无法解决所有的问题,因为Quantity 定义的变化可能会影响Quantity 类型变量的使用方式。我们至少需要改动使用了Quantity 类型变量的printf 函数调用和scanf 函数调用,用转换说明%ld 替换%d 。

C语言库自身使用typedef 为那些可能依据C语言实现的不同而不同的类型创建类型名;这些类型的名字经常以_t 结尾,比如ptrdiff_t 、size_t 和wchar_t 。这些类型的精确定义不尽相同,下面是一些常见的例子:

typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;

在C99中,<stdint.h> 头文件使用typedef 定义占用特定位数的整数类型名。例如,int32_t 是恰好占用32位的有符号整型。这是一种有效的定义方式,能使程序更易于移植。

7.6 sizeof 运算符

sizeof 运算符允许程序存储指定类型值所需空间的大小。表达式

[sizeof 表达式]         sizeof (类型名)

的值是一个无符号整数代表存储属于类型名的值所需要的字节数。表达式sizeof(char) 的值始终为1,但是对其他类型计算出的值可能会有所不同。在32位的机器上,表达式sizeof(int) 的值通常为4。注意,sizeof 运算符是一种特殊的运算符, 因为编译器本身通常就能够确定sizeof 表达式的值

通常情况下,sizeof 运算符也可以应用于常量、变量和表达式。如果i 和 j 是整型变量,那么sizeof(i) 在32位机器上的值为4,这和表达式sizeof(i+j) 的值一样。跟应用于类型时不同,sizeof应用于表达式时不要求圆括号,我们可以用sizeof i 代替sizeof(i) 。但是,由于运算符优先级的问题,圆括号有时还是需要的编译器会把表达式sizeof i + j 解释为(sizeof i) + j,这是因为sizeof 作为一元运算符的优先级高于二元运算符+ 。为了避免出现此类问题,我们在sizeof 表达式中始终加上圆括号

显示sizeof 值时要注意,因为sizeof 表达式的类型是size_t ,这是一种由实现定义的类型。在C89中,最好在显示前把表达式的值转换成一种已知的类型。size_t 一定是无符号整型,所以最安全的方法是把sizeof 表达式强制转换成unsigned long 类型(C89中最大的无符号类型),然后使用转换说明符%lu 显示

printf("Size of int: %lu\n", (unsigned long) sizeof(int));

C99中,size_t 类型可以比 unsigned long 更长。但C99中的printf 可以直接显示出size_t 类型值而不需要强制转换。方法是在转换说明中的一般整数(通常用u )代码前使用字母z :

printf("Size of int: %zu\n", sizeof(int)); /* C99 only */

问答

1、浮点常量为什么存储成double 格式而不是float 格式?

答:由于历史的原因,C语言更倾向于使用double 类型,float 类型则被看成是“二等公民”。思考Kernighan和Ritchie的The CProgramming Language 一书中关于float 的论述:“使用float 类型的主要原因是节省大型数组的存储空间,或者有时是为了节省时间,因为在一些机器上双精度计算的开销格外大。”经典C要求所有浮点计算都采用双精度的格式。(C89和C99没有这样的要求。)

这让我联系到,现在AI大模型精度用的就是浮点类型fp8、fp16等,用它的原因之一就是能大幅节省存储空间。

2、为什么C语言要提供类型定义呢?定义一个BOOL 宏不是和用typedef 定义一个Bool 类型一样好用吗?

答:类型定义和宏定义存在两个重要的不同点。首先,类型定义比宏定义功能更强大。特别是,数组和指针类型是不能定义为宏的
假设我们试图使用宏来定义一个“指向整数的指针”类型:
#define PTR_TO_INT int *
声明
PTR_TO_INT p, q, r;
在处理以后将会变成
int * p, q, r;
可惜的是,只有p 是指针,q 和r 都成了普通的整型变量。类型定义不会有这样的问题。

其次,typedef 命名的对象具有和变量相同的作用域规则;定义在函数体内的typedef 名字在函数外是无法识别的。另一方面,宏的名字在预处理时会在任何出现的地方被替换掉。

练习题

7.1节

1. 给出下列整数常量的十进制值。
(a) 077
(b) 0x77
(c) 0XABC

(a)这是八进制数,按权值展开即可:

077=7\times 8^{1}+7\times 8^{0}=56+7=63

(b)这是十六进制数,按权值展开即可:

0\times 77=7\times16^1 + 7\times16^0=112+7=119

(c)这是十六进制数,按权值展开即可:

0\times ABC=A\times16^2 + B\times16^1 +C\times16^0= 10\times16^2 + 11\times16^1 +12\times16^0=2560+176+12=2748

7.2节

2. 下列哪些常量在C语言中不是合法的?区分每一个合法的常量是整数还是浮点数。

(a) 010E2
(b) 32.1E+5
(c) 0790
(d) 100_000
(e) 3.978e-2

(a)推测:八进制表示,后面是十进制指数,可能不合法。

验证:输出了1000,推测错误。

修正:因为后面E明确为科学计数法,故010被认为是10进制浮点数,会被识别成double类型的浮点数。

(b) 32.1E+5,合法。E后面阶码为+5,为double类型浮点数。

验证:输出3210000.000000,推测正确。

(c) 0790,不合法。8进制数,这里9超过了基数范围。

验证: error: invalid digit "9" in octal constant<stdin>

(d)100_000,合法,整型变量,但要看编译器支持情况(C11之后支持)。

C99之前会报错:[Error] invalid suffix "_000" on integer constant

(e) 正确,e科学计数法,阶码为-2。被识别成double类型的浮点数。

3、下列哪些类型在C语言中不是合法的?

(a) short unsigned int
(b) short float
(c) long double
(d) unsigned long

(a)合法, 无符号短整型。

(b)不合法,不可以类型不能定义为短整型又定义为单精度浮点型。

[Error] both 'short' and 'float' in declaration specifiers

(c)合法,c99的长浮点型

(d)合法,无符号长整型

7.3节

4. 如果变量c是char 类型,那么下列哪条语句是非法的?

(a) i += c; /* i has type int */
(b) c = 2 * c - 1;
(c) putchar(c);
(d) printf(c);

(a) 合法

(b)合法

(c)合法

(d)不合法,需要格式说明符。

5. 下列哪条不是书写数65的合法方式?(假设字符集是ASCII。)

(a) 'A'
(b) 0b1000001 = 65
(c) 0101 = 65
(d) 0x41 = 65

#include <stdio.h>int main(void)
{printf("%d\n", 'A');printf("%d\n", 0b1000001);printf("%d\n", 0101);printf("%d\n", 0x41);return 0;
}

都能打印65

6. 对于下面的数据项,指明char 、short 、int 、long 类型中哪种类型是足以存储数据的最小类型。
(a) 一个月的天数
(b) 一年的天数
(c) 一天的分钟数
(d) 一天的秒数

(a)1-31 => char

(b) 366 => short

(c) 1440 => short

(d) 86400 => int/long

7.4节

9. 假设变量i 和变量j 都是int 类型,那么表达式i / j +'a' 是什么类型?

(i / j) => int类型

(i / j) + 'a','a'由char类型提升到int类型,所以最后为int类型。

10.假设变量i 是int 类型,变量j 是long int 类型,并且变量k 是unsigned int 类型,那么表达式i + (int)j * k 是什么类型?

(int)j被强制转为int类型,j*k中,j再被提升到unsigned int类型,最后i也被提升到unsigned int类型。故最后是unsigned int类型。(前面提到int不要与unsigned int混合计算,负数计算或比较会出现意外情况)

11.假设变量i 是int 类型,变量f 是float 类型,变量d 是double 类型,那么表达式i * f / d 是什么类型?

double类型。

12. 假设变量i 是int 类型,变量f 是float 类型,变量d 是double 类型,请解释在执行下列语句时发生了什么转换?

d = i + f;

int - > float -> double

13. 假设程序包含下列声明:

char c = '\1';
short s = 2;
int i = -3;
long m = 5;
float f = 6.5f;
double d = 7.5;

给出下列每个表达式的值和类型。
(a) c * i   (c) f / c   (e) f - d
(b) s + m   (d) d / s   (f) (int) f

(a)int

(b)long

(c)float

(d)double

(e)double

(f)int

14. 下列语句是否总是可以正确地计算出f 的小数部分(假设f 和frac_part 都是float 类型的变量)?
frac_part = f - (int) f;

如果不是,那么出了什么问题?

数字极小会出现精度问题,所以更好的方法是使用modf函数

7.5节

15. 使用typedef 创建名为Int8 、Int16 和Int32 的类型。定义这些类型以便它们可以在你的机器上分别表示8位、16位和32位的整数。


#include <stdint.h>// 定义跨平台的8/16/32位整数类型
typedef int8_t   Int8;   // 精确8位有符号整数
typedef uint8_t  UInt8;  // 精确8位无符号整数
typedef int16_t  Int16;  // 精确16位有符号整数
typedef uint16_t UInt16; // 精确16位无符号整数
typedef int32_t  Int32;  // 精确32位有符号整数
typedef uint32_t UInt32; // 精确32位无符号整数int main(void)
{return 0;
}

编程题

1. 如果i * i 超出了int 类型的最大取值,那么6.3节的程序square2.c 将失败(通常会显示奇怪的答案)。运行该程序,并确定导致失败的 n 的最小值。尝试把变量i 的类型改成short 并再次运行该程序。(不要忘记更新printf 函数调用中的转换说明!)然后尝试改成long 。从这些实验中,你能总结出在你的机器上用于存储整数类型的位数是多少吗?

/* Prints a table of squares using a for statement */
#include <stdio.h>
int main(void)
{int i;short n;printf("This program prints a table of squares.\n");printf("Enter number of entries in table: ");scanf("%hd", &n);for (i = 1; i <= n; i++)printf("%10d%10d\n", i, i * i);return 0;
}

2. 修改6.3节的程序square2.c ,每24次平方后暂停并显示下列信息:

Press Enter to continue...

显示完上述消息后,程序应该使用getchar 函数读入一个字符。getchar 函数读到用户录入的回车键才允许程序继续。

/* Prints a table of squares using a for statement */
#include <stdio.h>
int main(void)
{int i, n;char ch;printf("This program prints a table of squares.\n");printf("Enter number of entries in table: ");scanf("%d", &n);getchar(); //要吃掉scanf返还的换行符for (i = 1; i <= n; i++){printf("%10d%10d\n", i, i * i);if(i % 24 == 0)while((ch = getchar()) != '\n'); }return 0;
}

3. 修改7.1节的程序sum2.c ,对double 型值组成的数列求和。

/* Sums a series of numbers (using long int variables) */
#include <stdio.h>
#include <math.h>
int main(void)
{double n, sum = 0.0;printf("This program sums a series of numbers.\n");printf("Enter numbers (0 to terminate): ");scanf("%lf", &n);while (fabs(n) >= 1e-9) {sum += n;scanf("%lf", &n);}printf("The sum is: %f\n", sum);return 0;
}

4. 编写程序可以把字母格式的电话号码翻译成数值格式:

Enter phone number: CALLATT
2255288

(如果没有电话在身边,参考这里给出的字母在键盘上的对应关系:2=ABC ,3=DEF ,4=GHI ,5=JKL ,6=MNO ,7=PQRS ,8=TUV ,9=WXYZ 。)原始电话号码中的非字母字符(例如数字或标点符号)保持不变:

Enter phone number: 1-800-COL-LECT
1-800-265-5328

可以假设任何用户输入的字母都是大写字母。

#include <stdio.h>
#include <ctype.h>
int main(void)
{char c;printf("Enter phone number: ");while((c = getchar()) != '\n'){if(isalpha(c) && isupper(c)){switch (c) {case 'A': case 'B': case 'C':putchar('2');break;case 'D': case 'E': case 'F':putchar('3');break;case 'G': case 'H': case 'I':putchar('4');break;case 'J': case 'K': case 'L':putchar('5');break;case 'M': case 'N': case 'O':putchar('6');break;case 'P': case 'Q': case 'R': case 'S':putchar('7');break;case 'T': case 'U': case 'V':putchar('8');break;case 'W': case 'X': case 'Y': case 'Z':putchar('9');break;default: break;}}else putchar(c);}return 0;
}

5. 在十字拼字游戏中,玩家利用小卡片组成单词,每个卡片包含字母和面值。面值根据字母稀缺程度的不同而不同。(面值有:1——AEILNORSTU,2——DG,3——BCMP,4——FHVWY,5——K,8——JX,10——QZ。)编写程序通过对单词中字母的面值求和来计算单词的值:

Enter a word: pitfall
Scrabble value: 12

编写的程序应该允许单词中混合出现大小写字母。提示 :使用toupper 库函数。

#include <stdio.h>
#include <ctype.h>
int main(void)
{char c;int v = 0;printf("Enter a word: ");while((c = getchar()) != '\n'){c = tolower(c);switch(c){case 'a': case 'e': case 'i': case 'l': case 'n': case 'o':	case 'r': case 's': case 't': case 'u':v++;break;case 'd': case 'g':v += 2;break;case 'b': case 'c': case 'm': case 'p':v += 3;break;case 'f': case 'h': case 'v': case 'w': case 'y':v += 4;break;case 'k':v += 5;break;case 'j': case 'x':v += 8;break;case 'q': case 'z':v += 10;break;								}}printf("Scrabble value: %d", v);return 0;
}

6. 编写程序显示sizeof(int) 、sizeof(short) 、sizeof(long) 、sizeof(float) 、sizeof(double) 和sizeof(long double) 的值。

#include <stdio.h>
int main(void)
{printf("sizeof(int) = %zu\n", sizeof(int));printf("sizeof(short) = %zu\n", sizeof(short));printf("sizeof(long) = %zu\n", sizeof(long));printf("sizeof(long long) = %zu\n", sizeof(long long));printf("sizeof(float) = %zu\n", sizeof(float));printf("sizeof(double) = %zu\n", sizeof(double));printf("sizeof(long double) = %zu\n", sizeof(long double));return 0;
}

7. 修改第3章的编程题6,使得用户可以对两个分数进行加、减、乘、除运算(在两个分数之间输入+ 、- 、* 或/ 符号)。

#include <stdio.h>int main(void)
{int num1, denom1, num2, denom2, result_num, result_denom;char sign;printf("Enter two fractions separated by a sign:");scanf("%d/%d%c%d/%d", &num1, &denom1, &sign, &num2, &denom2);switch(sign){case '+':result_num = num1 * denom2 + num2 * denom1; //通分-分子result_denom = denom1 * denom2; //通分-分母break;case '-':result_num = num1 * denom2 - num2 * denom1; //通分-分子result_denom = denom1 * denom2; //通分-分母break;case '*':result_num = num1 * num2; //通分-分子result_denom = denom1 * denom2; //通分-分母break;	case '/':result_num = num1 * denom2;result_denom = denom1 * num2;break;default:break;	}printf("The result is %d/%d\n", result_num, result_denom);return 0;
}

8. 修改第5章的编程题8,要求用户输入12小时制的时间。输入时间的格式为时∶分 后跟A、P、AM或PM(大小写均可)。数值时间和AM/PM之间允许有空白(但不强制要求有空白)。有效输入的示例如下:

1:15P
1:15PM
1:15p
1:15pm
1:15 P
1:15 PM
1:15 p
1:15 pm

可以假定输入的格式就是上述之一,不需要进行错误判定。

#include <stdio.h>
#include <ctype.h>
int main(void)
{int h, m;int t;char c;printf("Enter a 12-hour time: ");scanf("%d:%d %c", &h, &m, &c);t = toupper(c) == 'A' ? h * 60 + m : h * 60 + m + 12 * 60;if(t < (480 + 583) / 2)printf("Closest departure time is 8:00 a.m., arriving at 10:06 a.m.");else if (t < (583 + 679) / 2)printf("Closest departure time is 9:43 a.m., arriving at 11:52 a.m.");else if(t < (679 + 767) / 2)printf("Closest departure time is 11:19 a.m., arriving at 1:31 p.m.");else if(t < (767 + 840) / 2)printf("Closest departure time is 12:47 a.m., arriving at 3:00 p.m.");else if(t < (840 + 945) / 2)printf("Closest departure time is 2:00 p.m., arriving at 4:08 p.m.");else if(t < (945+1140) / 2)printf("Closest departure time is 3:45 p.m., arriving at 5:55 p.m.");else if(t < (1140 + 1305) / 2)printf("Closest departure time is 7:00 p.m., arriving at 9:20 p.m.");else if(t < (135 + 480) / 2)printf("Closest departure time is 9:45 p.m., arriving at 11:58 p.m.");else printf("Closest departure time is 9:45 p.m., arriving at 11:58 p.m.");return 0;
}

9. 编写程序要求用户输入12小时制的时间,然后用24小时制显示该时间:

Enter a 12-hour time: 9:11 PM
Equivalent 24-hour time: 21:11

参考编程题8中关于输入格式的描述。

#include <stdio.h>
#include <ctype.h>
int main(void) {int h, m;char c;printf("Enter a 12-hour time: ");scanf("%d:%d %c", &h, &m, &c);if (toupper(c) == 'A') {printf("Equivalent 24-hour time: %d:%d", h, m);}else {printf("Equivalent 24-hour time: %d:%d", h + 12, m);}return 0;
}

10. 编写程序统计句子中元音字母(a、e、i、o、u)的个数:

Enter a sentence: And that's the way it is.
Your sentence contains 6 vowels.

#include <stdio.h>
#include <ctype.h>int main(void) {int cnt = 0;char c;printf("Enter a sentence: ");while ((c = getchar()) != '\n') {switch(toupper(c)){case 'A': case 'E': case 'I': case 'O' : case 'U':cnt++;break;default:break;}}printf("Your sentence contains %d vowels.", cnt);return 0;
}

11. 编写一个程序,根据用户输入的英文名和姓先显示姓氏,其后跟一个逗号,然后显示名的首字母,最后加一个点:

Enter a first and last name: Lloyd Fosdick
Fosdick, L.

用户的输入中可能包含空格(名之前、名和姓之间、姓氏之后)。

#include <stdio.h>
#include <ctype.h>int main(void) {char f, c;printf("Enter a first and last name: ");while(!isalpha(f = getchar()));while((c = getchar()) != ' ');while((c = getchar()) == ' ');putchar(c);while((c = getchar()) != ' '  && (c != '\n')){putchar(c);}printf(", %c.", toupper(f));return 0;
}

12. 编写程序对表达式求值:

Enter an expression: 1+2.5*3
Value of expression: 10.5

表达式中的操作数是浮点数,运算符是+ 、- 、* 和/ 。表达式从左向右求值(所有运算符的优先级都一样)。

 

#include <stdio.h>int main(void) {char sign;float n, r = 0.0f;printf("Enter an expression: ");scanf("%f", &r);while ((sign = getchar()) != '\n') {scanf("%f", &n);if (sign == '+') r += n;else if (sign == '-') r -= n;else if (sign == '*') r *= n;else if (sign == '/') r /= n;}printf("Value of expression: %g\n", r);return 0;
}

13. 编写程序计算句子的平均词长:

Enter a sentence: It was deja vu all over again.
Average word length: 3.4

简单起见,程序中把标点符号看作其前面单词的一部分。平均词长显示一个小数位。

#include <stdio.h>int main(void) {char c;int words = 0, cnt = 0;printf("Enter a sentence: ");while((c = getchar()) != '\n'){if(c == ' ' || c == '.') words++;if(c != ' ') cnt++;}printf("Average word length: %.1f", cnt * 1.0f / words);return 0;
}

14. 编写程序,用牛顿方法计算正浮点数的平方根:

Enter a positive number: 3
Square root: 1.73205

设 x是用户输入的数。牛顿方法需要先给出 x平方根的猜测值y(我们使用1)。后续的猜测值通过计算 y和 x/y的平均值得到。下表给出了求解3的平方根的过程。

xyx/yy和x/y的平均值
3132
321.51.75
31.751.714291.73214
31.732141.731961.73205
31.732051.732051.73205

注意, y的值逐渐接近x的平方根。为了获得更高的精度,程序中应使用double 类型的变量代替float 类型的变量。当y的新旧值之差的绝对值小于0.00001和y的乘积时程序终止。提示 :调用fabs 函数求double 类型数值的绝对值。(为了使用fabs 函数,需要在程序的开头包含<math.h> 头。)

#include <stdio.h>
#include <math.h>
int main(void) {int x;double y = 1.0, div, avg = y;printf("Enter a positive number: ");scanf("%d", &x);do{y = avg;div = x / y;avg = (y + div) / 2.0;}while(fabs(y - avg) >= 1E-5 * y);printf("Square root: %g", y);return 0;
}

15. 编程计算正整数的阶乘:

Enter a positive integer: 6
Factorial of 6: 720

(a) 用short 类型变量存储阶乘的值。为了正确打印出n的阶乘,n的最大值是多少?
(b) 用int 类型变量重复(a)。
(c) 用long 类型变量重复(a)。
(d) 如果你的编译器支持long long 类型,用long long 类型变量重复(a)。
(e) 用float 类型变量重复(a)。
(f) 用double 类型变量重复(a)。
(g) 用long double 类型变量重复(a)。
在(e)~(g)几种情况下,程序会显示阶乘的近似值,不一定是准确值。

(a)

#include <stdio.h>
#include <math.h>
int main(void) {int n;short fact = 1;printf("Enter a positive integer: ");scanf("%d", &n);for(int i = 1; i <= n; i++){fact *= i;}printf("Factorial of %d: %hd", n, fact);
}

最多能算到7!

(b)最多能算到12!

(c)最多能算到12!

(d)最多能算到20!

(e) float类型(约6-7位有效数字):

最大n=34(之后精度显著下降)

(f) double类型(约15-16位有效数字):

最大n=170(171!会溢出为inf)

(g) long double类型(通常80位,约18-19位有效数字):

最大n=1754(具体值取决于编译器实现)

下次准备更新第8章-数组

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/919968.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919968.shtml
英文地址,请注明出处:http://en.pswp.cn/news/919968.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

K8S的部署与常用管理

一、k8s的部署 1.1.集群环境初始化 1.1.1.所有主机禁用swap [rootk8s- ~]# systemctl mask dev-nvme0n1p3.swap [rootk8s- ~]# swapoff -a [rootk8s- ~]# systemctl status dev-nvme0n1p3.swap [rootk8s- ~]# vim /etc/fstab 内容&#xff1a; 注释swap 1.1.2.安装k8s部署工…

2025年机械工程与自动化技术国际会议(ICMEAT 2025)

2025年机械工程与自动化技术国际会议&#xff08;ICMEAT 2025&#xff09; 2025 International Conference on Mechanical Engineering and Automation Technology一、大会信息会议简称&#xff1a;ICMEAT 2025 大会地点&#xff1a;中国杭州 审稿通知&#xff1a;投稿后2-3日内…

高数 不定积分(4-3):分部积分法

文章目录写在前面分部积分法&#x1f615; 一个小问题✨ 分部积分法是怎么来的&#xff1f;&#x1f330; 几个小例子⭐ 最终总结&#xff01;后话写在前面 文章传送门&#xff1a;高数 不定积分&#xff08;4-2&#xff09;&#xff1a;换元积分法 今天再更一篇:) 上篇文章&…

Chrome/360 浏览器 WebUI 资源底层机制解析:共享资源与专属资源的奥秘

在 Chromium 和 360 浏览器源码中&#xff0c;我们会发现 WebUI 页面不仅有 C 逻辑处理&#xff08;如 WebUIMessageHandler&#xff09;&#xff0c;还伴随着大量 HTML、CSS 和 JS 文件。尤其是 src/ui/webui/resources 和 src/chrome/browser/360/webui 这两个目录&#xff0…

基于springboot的高校后勤保修服务系统/基于android的高校后勤保修服务系统app

基于springboot的高校后勤保修服务系统/基于android的高校后勤保修服务系统app

Qt QML 用Q_PROPERTY快捷访问c++属性

在之前我写过如何调用函数&#xff0c;当时的属性都是手搓的&#xff0c;也就是自己写成员变量、变化信号和读写函数&#xff0c;但其实有一个很便捷的方法&#xff0c;即使用Q_PROPERTY&#xff0c;下面给出标准结构&#xff1a;Q_PROPERTY(数据类型 变量名 READ 变量名 WRITE…

ubuntu中网卡的 IP 及网关配置设置为永久生效

要将 Ubuntu 中 ens33 和 ens36 网卡的 IP 及网关配置设置为永久生效&#xff08;重启后不丢失&#xff09;&#xff0c;需通过 netplan 配置并禁用 cloud-init 对网络的干扰&#xff08;避免重启后配置被覆盖&#xff09;&#xff0c;具体步骤如下&#xff1a;一、最终的永久生…

不再让Windows更新!Edge游戏助手卸载及关闭自动更新

文章目录Windows系统更新问题方法一&#xff1a;通过注册表手动设置1. 打开注册表编辑器2. 定位到目标路径3. 创建新的DWORD值4. 修改数值方法二&#xff1a;命令行设置1. 打开命令提示符2. 输入命令验证设置是否生效恢复更新Edge关闭游戏助手Edge关闭后台运行Edge关闭自动更新…

css3之flex布局

flex布局要牢记的两个知识点&#xff1a; 开启了flex布局的元素叫flex containerflex container里面的直接子元素叫flex items 这两点要记牢&#xff0c;设置属性的时候才不会搞混这个是flex布局的整体图 一、flex container上的属性 1.flex-direction 修改主轴方向的属性&…

vscode 搭建C/C++开发环境搭建(linux)

1.编译器/调试器安装首先&#xff0c;需要安装编译器&#xff08;GCC/G&#xff09;和调试器&#xff08;GDB&#xff09;,用于编译和调试代码。1.打开终端(Ctrl Alt T)2.更新软件包获取新版本信息sudo apt update3.安装build-essential包,它包含gcc,g等必要库sudo apt install…

vue-pure-admin页面引入和功能添加流程解析

vue-pure-admin (opens new window)是一款开源完全免费且开箱即用的中后台管理系统模版。完全采用 ECMAScript 模块&#xff08;ESM&#xff09;规范来编写和组织代码&#xff0c;使用了最新的 Vue3、Vite、Element-Plus、TypeScript、Pinia、Tailwindcss 等主流技术开发 以下是…

vlc-android: 编译自己的libvlc

概述 VLC 媒体播放器作为一款由志愿者开发团队精心维护的自由、开源且跨平台的多媒体播放器&#xff0c;能轻松驾驭绝大多数多媒体文件&#xff0c;无论是本地磁盘中的视频、音频&#xff0c;还是来自网络的流媒体协议. VLC for Android 支持网络串流&#xff0c;无论是基于 H…

并联谐振与串联谐振

在LC电路中&#xff0c;感抗和容抗相等时对应的频率值称为谐振频率&#xff0c;在接收广播电视信号或无线通信信号时&#xff0c;使接收电路的频率与所选择的发射的信号频率相同就叫做调谐。并联谐振LC并联谐振电路是指将电感器和电容器并联形成&#xff0c;如图所示。在并联谐…

打印机怎么连接电脑?打印机驱动?【图文详解】USB连接打印机?wifi连接打印机?

一、问题背景 在日常办公与生活里&#xff0c;把电脑和打印机连接起来&#xff0c;是实现文档、照片等打印的基础操作。但很多人初次尝试时&#xff0c;会因不熟悉流程而感到无从下手。 无论是办公场景下急需打印重要文件&#xff0c;还是日常生活中想要打印照片留念&#xff0…

CVPR 2025 | 医学影像加速进化:深度学习×多模态,精准诊断再升级

关注gongzhonghao【CVPR顶会精选】今天聊一个医学图像领域的前沿探索&#xff1a;结合空间感知卷积、扩散模型与视觉语言模型&#xff0c;从图像配准到合成分割&#xff0c;再到跨模态理解&#xff0c;打造了一个更加智能、鲁棒且可泛化的医学影像工具链。无论是SACB-Net带来的…

[每周一更]-(第157期):深入理解Go语言的垃圾回收机制:调优与监控

Go语言以其简洁的语法和强大的并发能力而闻名&#xff0c;而它的垃圾回收&#xff08;GC&#xff09;机制则是支撑其高性能的关键组件之一。本文将深入探讨Go语言的垃圾回收原理&#xff0c;并介绍如何对其进行调优与监控&#xff0c;以提升应用程序的性能。 Go语言垃圾回收机制…

Java 学习笔记(基础篇9)

1. 综合练习题目 1 &#xff1a;金额转换为中文大写格式请编写一个 Java 程序&#xff0c;实现将数字金额转换为中文大写格式&#xff08;带单位&#xff09;的功能&#xff0c;具体要求如下&#xff1a;(1) 程序接收用户输入的一个整数金额&#xff08;范围&#xff1a;0-9999…

云原生俱乐部-k8s知识点归纳(5)

写到这里&#xff0c;k8s的内容已经到一半了&#xff0c;虽然后面的内容我觉得更加玄学一点。控制器真的是个神奇的东西&#xff0c;虽然后面的CRD会带着大家一起做一个控制器&#xff0c;但是还是觉得很奇妙。控制器大概就是k8s中的精华了&#xff0c;通过控制器去监听k8s中ap…

C++复习2

C继承 继承的概念 继承&#xff08;inheritance&#xff09;机制是面向对象程序设计使代码可以复用的重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称为派生类。 继承呈现了面向对象程序设计…

ZKmall模块商城的跨境电商支付安全方案:加密与权限的双重防护

跨境电商支付环节面临双重挑战&#xff1a;一方面&#xff0c;不同国家的支付协议、货币结算规则差异显著&#xff0c;需满足多币种、多渠道的支付需求&#xff1b;另一方面&#xff0c;跨境数据传输的安全性与操作权限的严格管控直接关系到资金安全与合规性。ZKmall 模块商城针…