16.C预处理器和C库

news2024/9/24 23:32:29

文章目录

  • C预处理器和C库
    • 16.1翻译程序的第一步
    • 16.2明示常量:#define
    • 16.3在#define中使用参数
      • 16.3.1用宏参数创建字符串:#运算符
      • 16.3.2预处理器黏合剂:##运算符
      • 16.3.3变参宏:...和__VA_ARGS__
    • 16.4宏和函数的选择
    • 16.5文件包含:#include
      • 16.5.2使用头文件
    • 16.6其他指令
      • 16.6.1#undef指令
      • 16.6.2从c预处理器角度看已定义
      • 16.6.3条件编译
        • 1.#ifdef、#else和#endif指令
        • 2.#ifndef指令
        • 3.#if和#elif指令
      • 16.6.4预定义宏
      • 16.6.5#line和#error
      • 16.6.6#pragma
      • 16.6.7泛型选择(C11)
    • 16.7内联函数(C99)
    • 16.8_Noreturn函数(C11)

C预处理器和C库

C预处理器在程序执行之前查看程序。根据程序中的预处理器指令,预处理器把符号缩写替换成其表示的内容。预处理器可以包含程序所需的其他文件,可以选择让编译器查看哪些代码。预处理器并不知道c。基本上它的工作是把一些文本转换成另外一些文本

16.1翻译程序的第一步

预处理之前,编译器必须对该程序进行一些翻译处理:

  1. 编译器把源代码中出现的字符映射到源字符集。该过程处理多字节字符和三字符序列。
  2. 编译器定位每个反斜杠后面跟着换行符(不是\n,而是通过按下enter键在源代码文件中换行所生成的字符)的实例,并删除它们。由于预处理表达式的长度必须是一个逻辑行,所以这一步为预处理器做好了准备工作。一个逻辑行可以是多个物理行。
printf("That's wond\
        erful!\n");
// 转换成一个逻辑行:
printf("That's wonderful!\n");
  1. 编译器把文本划分成预处理记号序列、空白序列和注释序列(记号是由空格、制表符或换行符分隔的项)。要注意的是,编译器将用一个空格字符替换每一条注释。而且,实现可以用一个空格替换所有的空白字符序列(不包括换行符)。
  2. 程序已经准备好进入预处理阶段,预处理器查找一行中以#号开始的预处理指令。

16.2明示常量:#define

#define#作为一行的开始。ANSI和后来的标准都允许#前面有空格或制表符,而且还允许在#和指令的其余部分之间有空格。但是旧版的c要求指令从一行最左边开始,而且#和指令其余部分之间不能有空格。
指令可以出现在源文件的任何地方,其定义从指令出现的地方到该文件末尾有效。大量使用#define指令来定义明示常量(也叫作符号常量)。
预处理器指令从#开始运行,到后面的第1个换行符为止。也就是说,指令的长度仅限于一行。然而,在预处理开始前,编译器会把多行物理行处理为一行逻辑行

在这里插入图片描述

#include <stdio.h>

#define TWO 2   /* 可以使用注释,每条注释都会被一个空格代替 */
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde"    /* 反斜杠把该定义延续到下一行,注意,第2行要与第1行左对齐,否则会输出多余的空格 */
#define FOUR TWO*TWO
#define PX printf("X is %d.\n", x)
#define FMT "X is %d.\n"

int main(void) {
	// 变成了:int x = 2;
    int x = TWO;

	// 变成了:printf("X is %d.\n", x);
    PX;
    // 变成了:x = TWO*TWO;
    x = FOUR;

	// 变成了:printf("X is %d.\n", x);
    printf(FMT, x);
    // 变成了:printf("%s\n", "Consistency is the last refuge of the unimaginative. - Oscar Wilde");
    printf("%s\n", OW);
    // 不会变
    printf("TWO: OW\n");

    return 0;
}

由于编译器在编译期对所有的常量表达式(只包含常量的表达式)求值,所以预处理器不会进行实际的算术运算,这一过程在编译时进行。预处理器不做计算,不对表达式求值,它只进行替换

16.3在#define中使用参数

#define中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中。

在这里插入图片描述

#define SQUARE(X) X*X
// 看上去像函数调用,但是它的行为和函数调用完全不同。
z = SQUARE(2); 
#include <stdio.h>

#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n", X)

int main(void) {
    int x = 5;
    int z;

    printf("x = %d\n", x);
    z = SQUARE(x);
    printf("Evaluating SQUARE(x): ");
    PR(z);
    z = SQUARE(2);
    printf("Evaluating SQUARE(2): ");
    PR(z);
    printf("Evaluating SQUARE(x+2): ");
    // 此时,X*X替换成:x + 2*x + 2。可以将宏修改为:#define SQUARE(X) (X)*(X)以解决这样的问题。
    // 但是,并未解决所有的问题,例如下面的代码行。
    PR(SQUARE(x + 2));
    // 此时,X*X替换成:100 / 2*2(即便是使用上面优化后的宏)。因此,要解决这两种情况,
    // 要这样定义:#define SQUARE(X) ((X)*(X))。因此,必要时要使用足够多的圆括号来
    // 确保运算和结合的正确顺序。
    printf("Evaluating 100/SQUARE(2): ");
    PR(100 / SQUARE(2));
    printf("x is %d.\n", x);
    printf("Evaluating SQUARE(++x): ");
    // 但是,仍然无法避免这种情况,此时,X*X替换成:++x*++x(即便是使用上面优化后的宏),
    // 不同的编译器会有不同的结果。例如,42或者49。
    PR(SQUARE(++x));
    printf("After incrementing, x is %x.\n", x);

    return 0;
}

16.3.1用宏参数创建字符串:#运算符

C允许在字符串中包含宏参数。在类函数宏的替换体中,#号作为一个预处理运算符,可以把记号转换成字符串。

#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n", ((x)*(x)))

int main(void) {
	int y = 5;
	
	// 用"y"替换#x
	PSQR(y);
	// 用"2 + 4"替换#x
	PSQR(2 + 4);
	
	return 0;
}
/*输出结果:
The square of y is 25.
The square of 2 + 4 is 36.
*/

16.3.2预处理器黏合剂:##运算符

##运算符可用于类函数宏的替换部分。而且,##还可用于对象宏的替换部分。##运算符把两个记号组合成一个记号。

// 此时,XNAME(4)将展开为x4
#define XNAME(n) x ## n
#include <stdio.h>

#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);

int main(void) {
    int XNAME(1) = 14;  // 变成int x1 = 14;
    int XNAME(2) = 20;  // 变成int x2 = 20;
    int x3 = 30;

    PRINT_XN(1);    // 变成printf("x1 = %d\n", x1);
    PRINT_XN(2);    // 变成printf("x2 = %d\n", x2);
    PRINT_XN(3);    // 变成printf("x3 = %d\n", x3);

    return 0;
}

16.3.3变参宏:…和__VA_ARGS__

一些函数(如printf())接受数量可变的参数。stdvar.h头文件提供了工具,让用户自定义带可变参数的函数。C99/C11也对宏提供了这样的工具。
通常把宏参数列表中最后的参数写成省略号(...)来实现这一功能。这样,预定义宏__VA_ARGS__可用在替换部分中,表明省略号代表什么。

#define PR(...) printf(__VA_ARGS__)

// __VA_ARGS__展开为1个参数:"Howdy",展开后的代码是:printf("Howdy");
PR("Howdy");
// __VA_ARGS__展开为3个参数:"weight = %d, shipping = $%.2f\n"、wt、sp。
// 展开后的代码是:printf("weight = %d, shipping = $%.2f\n", wt, sp);
PR("weight = %d, shipping = $%.2f\n", wt, sp);
#include <stdio.h>
#include <math.h>

#define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)

int main(void) {
    double x = 48;
    double y;

    y = sqrt(x);
    // 展开后的代码是:printf("Message 1: x = %g\n", x);
    PR(1, "x = %g\n", x);
    // 展开后的代码是:printf("Message 2: x = %.2f, y = %.4f\n", x, y);
    PR(2, "x = %.2f, y = %.4f\n", x, y);

    return 0;
}

16.4宏和函数的选择

使用宏比使用普通函数复杂一些,稍有不慎会产生奇怪的副作用。一些编译器规定宏只能定义成一行。不过,即使编译器没有这个限制,也应该这样做。
宏和函数的选择实际上是时间和空间的权衡。宏产生内联代码,即在程序中生成语句。如果调用20次宏,即在程序中插入20行代码。如果调用函数20次,程序中只有一份函数语句的副本,所以节省了空间。然而另一方面,程序的控制必须跳转至函数内,随后再返回主调程序,这显然比内联代码花费更多的时间。
宏的一个优点是,不用担心变量类型(这是因为宏处理的是字符串,而不是实际的值)。因此,只要能用intfloat类型都可以用SQUARE(x)宏。
C99提供了第3中可替换的方法,内联函数。

对于简单的函数,通常使用宏:

#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
#define ABS(X) ((X) < 0 ? -(X) : (X))
#define ISSIGN(X) ((X) == '+' || (X) == '-' ? 1 : 0)

要记住以下几点:

  • 宏名中不允许有空格,但是再替换字符串中可以有空格。ANSI C允许在参数列表中使用空格。
  • 用圆括号把宏的参数和整个替换体括起来。这样能确保被括起来的部分能正确地展开:
forks = 2 * MAX(guests + 3, last);
  • 用大写字母表示宏函数的名称。该惯例不如用大写字母表示宏常量应用广泛。但是,大写字母可以提醒程序员注意,宏可能产生的副作用。
  • 如果打算使用宏来加快程序的运行速度,那么首先要确定使用宏和使用函数是否会导致较大差异。在程序中只使用一次的宏无法明显减少程序的运行时间。在嵌套循环中使用宏更有助于提高效率。

16.5文件包含:#include

16.5.2使用头文件

头文件中最常用的形式:

  • 明示常量
  • 宏函数
  • 函数声明
  • 结构模板定义
  • 类型定义

另外,还可以使用头文件声明外部变量供其他文件共享。例如,如果已经开发了共享某个变量的一系列函数,该变量报告某种状况(如,错误情况),这种方法就很有效。这种情况下,可以在包含这些函数声明的源代码文件定义一个文件作用域的外部链接变量:

int status = 0;	// 该变量具有文件作用域,在源代码文件。

然后,可以在与源代码文件相关联的头文件中进行引用式声明:

extern int status;	// 在头文件中

这行代码会出现在包含了该头文件的文件中,这样使用该系列函数的文件都能使用这个变量。虽然源代码文件中包含该头文件后也包含了该声明,但是只要声明的类型一致,在一个文件中同时使用定义式声明和引用式声明没问题。
需要包含头文件的另一种情况是,使用具有文件作用域、内部链接和const限定符的变量或数组。const防止值被意外修改,static意味着每个包含该头文件的文件都获得一份副本。因此,不需要在一个文件中进行定义式声明,在其他文件中进行引用式声明。

16.6其他指令

16.6.1#undef指令

#undef指令用于取消已定义的#define指令。

#define LIMIT 400
// 移出上面的定义,现在可以把LIMIT重新定义为一个新值。
#undef LIMIT

即使原来没有定义,取消定义仍然有效。如果想使用一个名称,又不确定之前是否已经用过,为安全起见,可以用#undef指令取消该名字的定义。

16.6.2从c预处理器角度看已定义

当预处理器在预处理器指令中发现一个标识符时,它会把该标识符当作已定义的或未定义的。这里的已定义表示由预处理器定义
已定义宏可以是对象宏,包括空宏或类函数宏:

#define LIMIT 1000	// LIMIT是已定义的
#define GOOD	// GOOD是已定义的
#define A(X) ((-(X))*(X))	// A是已定义的
int q;	// q不是宏,因此是未定义的。
#undef GOOD	// GOOD取消定义,是未定义的。

#define宏的作用域从它在文件中的声明处开始,直到用#undef指令取消宏为止,或延伸至文件尾(以二者中先满足的条件作为宏作用域的结束)。另外还要注意,如果宏通过头文件引入,那么#define在文件中的位置取决于#include指令的位置。

16.6.3条件编译

1.#ifdef、#else和#endif指令

// 如果使用旧的编译器,必须左对齐所有的指令或至少左对齐#号:
#ifdef MAVIS
	#include "horse.h"	// 如果已经用#define定义了MAVIS,则执行下面的指令。
	#define STABLES 5
#else
	#include "cow.h"	// 如果没有用#define定义MAVIS,则执行下面的指令。
// 必须存在
#endif

2.#ifndef指令

#ifndef指令判断后面的标识符是否是未定义的,常用于定义之前未定义的常量。

#ifndef SIZE
	#define SIZE 100
#endif

#ifndef指令通常用于防止多次包含一个文件。也就是说,应该这样设置头文件:

/* things.h,当预处理器第2次发现该文件被包含时,THINGS_H_是已定义的,预处理会跳过该文件的其他部分。 */
#ifndef THINGS_H_
	#define THINGS_H_
	/* 省略头文件中的其他内容 */
#endif

3.#if和#elif指令

#if后面跟整型常量表达式,如果表达式为非零,则表达式为真。可以在指令中使用c的关系运算符和逻辑运算符:

#if SYS == 1
#include "ibm.h"
#endif

可以按照if-else的形式使用#elif(早起的实现不支持#elif):

#if SYS == 1
	#include "ibmpc.h"
#elif SYS == 2
	#include "vax.h"
#elif SYS == 3
	#include "mac.h"
#else
	#include "general.h"
#endif

较新的编译器提供另一种方法测试名称是否已定义,即用#if defined (VAX)代替#ifdef VAX
这里,defined是一个预处理运算符,如果它的参数是用#define定义过,则返回1;否则返回0。这种新方法的优点是,它可以和#elif一起使用:

// 如果在VAX机上运行代码,则应该在文件前定义VAX:
#define VAX

#if defined (IBMPC)
	#include "ibmpc.h"
#elif defined (VAX)
	#include "vax.h"
#elif defined (MAC)
	#include "mac.h"
#else
	#include "general.h"
#endif

条件编译还有一个用途是让程序更容易移植。改变文件开头部分的几个关键的定义,即可根据不同的系统设置不同的值和包含不同的文件。

16.6.4预定义宏

在这里插入图片描述

C99标准提供一个名为__func__的预定义标识符,它展开为一个代表函数名的字符串(该函数包含该标识符)。那么,__func__必须具有函数作用域,而从本质上看宏具有文件作用域。因此,__func__是c语言的预定义标识符,而不是预定义宏。

16.6.5#line和#error

#line指令重置__LINE____FILE__宏报告的行号和文件名:

#line 1000	// 把当前行号重置为1000
#line 10 "cool.c"	// 把行号重置为10,把文件名重置为cool.c。

#error指令让预处理器发出一条错误消息,该消息包含指令中的文本。如果可能的话,编译过程应该中断:

// 如果编译器只支持旧标准,则会编译失败。
#if __STDC__VERSION__ != 201112L
#error Not C11
#endif

16.6.6#pragma

#pragma把编译器指令放入源代码中。

// 在开发C99时,标准被称为C9X,可以使用下面的编译指示让编译器支持C9X。一般而言,编译器都有自己的
// 编译指示集。例如,编译指示可能用于控制分配给自动变量的内存量,或者设置错误检查的严格程度,或者
// 启用非标准语言特性等。C99标准提供了3个标准编译指示,但是目前不在讨论范围中。
#pragma c9x on

C99还提供_Pragma预处理器运算符,该运算符把字符串转换成普通的编译指示:

_Pragma("nonstandardtreatmenttypeB on")
// 等价于:
#pragma nonstandardtreatmenttypeB on
// 由于该运算符不使用#符号,所以可以把它作为宏展开的一部分:
#define PRAGMA(X) _Pragma(#X)
#define LIMRG(X) PRAGMA(STDC CX_LIMITED_RANGE X)
// 然后,可以这样使用:
LIMRG( ON )
// 另一方面,下面的定义看上去没问题,但实际上无法正常运行。问题在于代码依赖字符串的串联功能,
// 而预处理过程完成之后才会串联字符串。
#define LIMRG(X) PRAGMA(STDC CX_LIMITED_RANGE #X)

_Pragma运算符完成解字符串的工作,即把字符串中的转义序列转换成它所代表的字符:

_Pragma("use_bool \"true \"false")
// 变成了:
#pragma use_bool "true "false

16.6.7泛型选择(C11)

C11新增了泛型选择表达式,可根据表达式的类型(即表达式的类型是intdouble还是其他类型)选择一个值。泛型选择表达式不是预处理器指令,但是在一些泛型编程中它常用作#define宏定义的一部分。

// 第1个项是一个表达式,后面的每个项都由一个类型、一个冒号和一个值组成。
// 第1个项的类型匹配哪个标签,整个表达式的值是该标签后面的值。
_Generic(x, int: 0, float: 1, double: 2, default: 3)
// 宏必须定义为一条逻辑行,但是可以用`\`把一条逻辑行分隔成多条物理行。
#define MYTYPE(x) _Generic((X), \
    int: "int",\
    float: "float",\
    double: "double",\
    default: "other"\
)

对一个泛型选择表达式求值时,程序不会先对第一个项求值,它只确定类型。只有匹配标签的类型后才会对表达式求值。

16.7内联函数(C99)

通常,函数调用都有一定的开销。使用宏使代码内联,可以避免这样的开销,但是也可能不起作用
标准规定具有内部链接的函数可以成为内联函数,还规定了内联函数的定义与调用该函数的代码必须在同一个文件中。因此,最简单的方法是使用函数说明符inline和存储类别说明static。通常,内联函数应定义在首次使用它的文件中,所以内联函数也相当于函数原型:

#include <stdio.h>

inline static void eatline() {	// 内联函数定义/原型
	while (getchar() != '\n') {
		continue;
	}
}

int main(void) {
	// ...
	eatline();	// 函数调用
	// ...
}

由于并未给内联函数预留单独的代码块,所以无法获得内联函数的地址(实际上可以获得地址,不过这样做之后,编译器会生成一个非内联函数)。另外,内联函数无法在调试器中显示。

编译器优化内联函数必须知道该函数定义的内容。这意味着内联函数定义与函数调用必须在同一个文件中。鉴于此,一般情况下内联函数都具有内部链接。因此,如果程序有多个文件都要使用某个内联函数,最简单的做法是,把内联函数定义放入头文件,并在使用该内联函数的文件中包含该头文件即可
一般都不在头文件中放置可执行代码,内联函数是个特例。因为内联函数具有内部链接,所以在多个文件中定义同一个内联函数不会产生什么问题。
与c++不同的是,c还允许混合使用内联函数定义和外部函数定义(具有外部链接的函数定义):

// file1.c
// ...

// inline static定义
inline static double square(double);

double square(double x) { return x * x; }

int main(void) {
	// 使用square()的局部static定义。由于该定义也是inline定义,
	// 所以编译器有可能优化代码,也许会内联该函数。
	double q = square(1.3);
	// ...
}

// file2.c
// ...

// 普通的函数定义(因此具有外部链接)
double square(double x) { return (int) (x*x); }

void spam(double v) {
	// 使用该文件中square()函数的定义,该定义具有外部链接,其他文件也可见。
	double kv = square(v);
	// ...
}

// file3.c
// ...

// inline定义,省略了static。
inline double square(double x) { return (int) (x * x + 0.5); }

void masp(double w) {
	// 编译器既可以使用该文件中square()函数的内联定义,也可以使用file2.c文件
	// 中的外部链接定义。如果像file3.c那样,省略file1.c文件inline定义中的static,
	// 那么该inline定义被视为可替换的外部定义。
	double kw = square(w);
	// ...
}

注意GCC在C99之前就使用一些不同的规则实现了内联函数,所以GCC可以根据当前编译器的标记来解释inline

16.8_Noreturn函数(C11)

函数说明符_Noreturn,表明调用完成后函数不返回主调函数。exit()函数是_Noreturn函数的一个示例。注意,void类型的函数在执行完毕后返回主调函数,只是它不提供返回值。
_Noreturn的目的是告诉用户和编译器,这个特殊的函数不会把控制返回主调程序。告诉用户以免滥用该函数,通知编译器可优化一些代码。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NCTF2022 calc题目复现

calc&#xff08;环境变量注入getshell&#xff09; 经典计算器题目&#xff0c;看着有点眼熟&#xff0c;没错&#xff0c;就是buu三月赛的一道题目。由于那时候web可能都算不上入门&#xff0c;所以也就没有复现。比赛时就网上看了看三月赛的wp&#xff0c;但是没有什么用&a…

IEEE 二进制浮点数的表示

今天&#xff0c;我来将 IEEE 二进制浮点数的表示方式进行一个简单的介绍。 浮点数 在 C 语言中&#xff0c;有两种存储浮点数的方式&#xff0c;分别是 float 和 double &#xff0c;当然了还有long double。这几种浮点型所容纳的长度不同&#xff0c;当然它们存储的精度也就…

[附源码]JAVA毕业设计新型药物临床信息管理系统(系统+LW)

[附源码]JAVA毕业设计新型药物临床信息管理系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 …

p5.第一章 Python基础入门 -- 运算符、优先级和表达式 (五)

1.2.3.2.11 False等价 False等价布尔值,相当于bool(value) 空容器 空集合set空字典dict空列表list空元组tuple空字符串None0# bool(value)是布尔函数# In: bool(1), bool(0) # Out: (True

离散数学·支配集、覆盖集、独立集和匹配

支配集 简而言之——V-支配集后剩下的点&#xff0c;都能在支配集中找到相邻的点 支配数的符号是γ0&#xff08;有关点的集&#xff0c;下标为0&#xff09; 例 右下角相同颜色的为同一个支配集 要注意极小性 整个V就是支配集&#xff08;所以说支配集找极大没有意义&#xf…

测试员凡尔赛,工作三年晒出11月工资条,直言加班太累了

最近有工作3年的测试员晒出自己11 月份的工资条&#xff0c;并直言加班太累了。 从工资条上可以看到&#xff0c;这个收入确实不算低&#xff0c;才3年时间&#xff0c;月工资就已经到了二万五了&#xff0c;这个工资已经可以击败绝大多数行业了。 不过二万五只是税前工资&am…

第二证券|系统性稳地产政策加力 租购并举制度加快建立

在房地产职业深度调整期&#xff0c;下一年方针走向备受关注。虽然日前召开的中心政治局会议没提及房地产&#xff0c;可是从其对经济方针的表述能够预见&#xff0c;作为经济支柱产业的房地产职业&#xff0c;下一年将在稳经济中发挥更重要的效果&#xff0c;国家将持续出台系…

[附源码]Python计算机毕业设计SSM基于的装修公司运营管理管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

一文彻底搞懂ssh的端口转发

文章目录背景什么是端口转发&#xff1f;本地端口转发本地端口转发的语法场景1场景二ssh -L参数解释ssh 远程端口的安全问题远程端口转发远程端口转发的语法场景一远程端口转发和本地端口转发要在哪台服务器上执行场景二ssh -R 参数解释端口转发的选项端口转发需要修改哪些ssh配…

fastdfs部署详解

fastdfs部署 官方github支持 官方解释&#xff1a;FastDFS 是一个开源的高性能分布式文件系统。它的主要功能包括&#xff1a;文件存储、文件同步和文件访问&#xff08;文件上传和文件下载&#xff09;&#xff0c;它可以解决高容量和负载均衡问题。FastDFS应该能满足图片分享…

10 款开源工具

1. JIRA 2. Git 3. Jenkins 4. Selenium 5. Groovy 6、Spock 7. Maven 8. Gradle 9. Docker 10. Linux 本文主要介绍Java程序员应该在2019年学习的一些基本和高级工具。如果你是一位经验丰富的Java开发人员&#xff0c;你可能对这些工具很熟悉&#xff0c;但如果不是&…

Java 并发编程<13>-ThreadPoolExecutor的springboot应用

Java 并发编程<13>-ThreadPoolExecutor的springboot应用 Java并发编程<10>安全集合 ...... Java 并发编程<1>-线程实现的方式 线程池简介 a .为什么使用线程池 降低系统资源消耗&#xff0c;通过重用已存在的线程&#xff0c;降低线程创建和销毁造成的消…

基于Surf+GTM的图像配准和拼接算法matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 SIFT采用的是DoG图像&#xff0c;而SURF采用的是Hessian矩阵&#xff08;SURF算法核心&#xff09;行列式近似值图像。在数学中&#xff0c;Hessian矩阵是一个自变量为向量的实值函数的二阶偏导数…

计算机网络(自顶向下)学习笔记——传输层

第三章—传输层 传输层位于应用层和网络层之间&#xff0c;是分层的网络体系结构中重要的部分&#xff0c;该层为运行在不同主机上的应用进程提供直接的通信服务起着至关重要的作用。在这里我们将讨论两个大的问题&#xff1a;将网络层在不同端系统之间的通信服务扩充到运行在…

机器学习-PCA主成份分析详细解说及代码实现

本站原创文章&#xff0c;转载请说明来自《老饼讲解-机器学习》ml.bbbdata.com 目录 一. PCA主成分分析思想介绍 1.1 主成份分析思想 1.2 什么是主成份 二. PCA主成分分析的数学描述 2.1 主成份分析的数学表达 2.2 主成份系数矩阵A的约定 2.3 主成份分析需要输出什么 三…

Linux网络编程(一)——基础篇

目录 一、网络结构模式 &#xff08;一&#xff09;C/S结构 &#xff08;二&#xff09;B/S结构 二、MAC地址 三、IP地址 &#xff08;一&#xff09;IP地址编址方式 A类IP地址 B类IP地址 C类IP地址 D、E类IP地址 特殊的网址 IP分类的优缺点 &#xff08;二&#x…

解决org.apache.jasper.JasperException: 无法为JSP编译类

1.问题描述 org.apache.jasper.JasperException: 无法为JSP编译类: 在运行java web项目时&#xff0c;启动tomcat服务器报这样的操作&#xff0c;一般就是tomcat版本跟jdk版本不兼容的问题。我用的是jdk17&#xff0c;经过查阅相关资料得出一般使用jdk8就可以解决此类问题 2.…

微信小程序 | 小程序配置和架构

&#x1f5a5;️ 微信小程序 专栏&#xff1a;小程序配置和架构 &#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; ✨ 个人主页&#xff1a;CoderHing的个人主页 &#x1f340; 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️ &#x1f44…

JavaScript:cookie和storage

cookie 用于客户端存储会话信息。在浏览器中会对cookie做一些限制&#xff1a; ❑ 不超过300个cookie&#xff1b; ❑ 每个cookie不超过4096字节&#xff1b; ❑ 每个域不超过20个cookie&#xff1b; ❑ 每个域不超过81920字节。 每个域能设置的cookie总数也是受限的&#xf…

(5)Pytorch数据处理

Pytorch 数据处理 要点总结 1、功能 Dataset&#xff1a;准备数据集&#xff0c;一般会针对自己的数据集格式重写Dataset&#xff0c;定义数据输入输出格式 Dataloader&#xff1a;用于加载数据&#xff0c;通常不用改这部分内容 2、看代码时请关注 Dataloader中collate_fn 传入…