C现代方法(第10章)笔记——程序结构
文章目录
- 第10章 程序结构
- 10.1 局部变量
- 10.1.1 静态局部变量
- 10.1.2 形式参数
- 10.2 外部变量
- 10.2.1 示例:用外部变量实现栈
- 10.2.2 外部变量的利与弊
- 10.3 程序块
- 10.4 作用域
- 10.5 构建C程序
- 10.5.1 复杂程序:给一手牌分类
- 问与答
- 写在最后
第10章 程序结构
——Will Rogers也一定会说:“没有自由变量这种东西。”
第9章已经介绍过函数了,因此本章就来讨论一个程序包含多个函数时所产生的几个问题。本章的前两节讨论局部变量(10.1节)和外部变量(10.2节)之间的差异,10.3节考虑程序块(含有声明的复合语句)问题,10.4节解决用于局部名、外部名和在程序块中声明的名字的作用域规则问题,10.5节介绍用来组织函数原型、函数定义、变量声明和程序其他部分的方法。
10.1 局部变量
我们把在函数体内声明的变量称为该函数的局部变量。在下面的函数中,sum是局部变量:
int sum_digits(int n)
{ int sum = 0; /* local variable */ while (n > 0) { sum += n % 10; n /= 10; } return sum;
}
默认情况下,局部变量具有下列性质:
自动存储期。变量的存储期(storage duration,也称为延续)是程序执行时,能够确保变量的存储空间必定存在的那一部分时间。通常来说,局部变量的存储空间是在包含该变量的函数被调用时“自动”分配的,函数返回时收回分配,所以称这种变量具有自动存储期。包含局部变量的函数返回时,局部变量的值无法保留。当再次调用该函数时,无法保证变量仍拥有原先的值。块作用域。变量的作用域是可以引用该变量的那一部分程序文本。局部变量拥有块作用域:从变量声明的点开始一直到所在函数体的末尾。因为局部变量的作用域不能延伸到其所属函数之外,所以其他函数可以把同名变量用于别的用途。
C99不要求在函数一开始就进行变量声明,所以局部变量的作用域可能非常小。
10.1.1 静态局部变量
在局部变量声明中放置单词static可以使变量具有静态存储期而不再是自动存储期。因为具有静态存储期的变量拥有永久的存储单元,所以在整个程序执行期间都会保留变量的值。思考下面的函数:
void f(void)
{ static int i; /* static local variable */ ...
}
因为局部变量
i已经声明为static,所以在程序执行期间它所占据的内存单元是不变的。在f返回时,变量i不会丢失其值。静态局部变量始终有块作用域,所以它对其他函数是不可见的。概括来说,静态变量是对其他函数隐藏数据的地方,但是它会为将来同一个函数的再调用保留这些数据。
10.1.2 形式参数
形式参数拥有和局部变量一样的性质,即自动存储期和块作用域。事实上,形式参数和局部变量唯一真正的区别是,在每次函数调用时对形式参数自动进行初始化(调用中通过赋值获得相应实际参数的值)。
10.2 外部变量
传递参数是给函数传送信息的一种方法。函数还可以通过外部变量(external variable)进行通信。外部变量是声明在任何函数体外的。
外部变量(有时称为全局变量)的性质不同于局部变量的性质。
静态存储期。就如同声明为static的局部变量一样,外部变量拥有静态存储期。存储在外部变量中的值将永久保留下来。文件作用域。外部变量拥有文件作用域:从变量被声明的点开始一直到所在文件的末尾。因此,跟随在外部变量声明之后的所有函数都可以访问(并修改)它。
10.2.1 示例:用外部变量实现栈
为了说明外部变量的使用方法,一起来看看称为
栈(stack)的数据结构。(栈是抽象的概念,它不是C语言的特性。大多数编程语言都可以实现栈。)像数组一样,栈可以存储具有相同数据类型的多个数据项。然而,栈操作是受限制的:只可以往栈中压入数据项(把数据项加在一端——“栈顶”)或者从栈中弹出数据项(从同一端移走数据项)。禁止测试或修改不在栈顶的数据项。
C语言中实现栈的一种方法是把元素存储在数组中,我们称这个数组为contents。命名为top的一个整型变量用来标记栈顶的位置。栈为空时,top的值为0。为了往栈中压入数据项,可以把数据项简单存储在contents中由top指定的位置上,然后自增top。弹出数据项则要求自减top,然后用它作为contents的索引取回弹出的数据项。基于上述这些概要,这里有一段代码(不是完整的程序)为栈声明了变量
contents和top并且提供了一组函数来表示对栈的操作。全部5个函数都需要访问变量top,而且其中2个函数还都需要访问contents,所以接下来把contents和top设为外部变量。
#include <stdbool.h> /* C99 only */
#define STACK_SIZE 100 /* external variables */
int contents[STACK_SIZE];
int top = 0; void make_empty(void)
{ top = 0;
} bool is_empty(void)
{ return top == 0;
} bool is_full(void)
{ return top == STACK_SIZE;
} void push(int i)
{ if(is_full()) stack_overflow(); else contents[top++] = i;
} int pop(void)
{ if(is_empty()) stack_underflow(); elsereturn contents[--top];
}
10.2.2 外部变量的利与弊
在多个函数必须共享一个变量时或者少数几个函数共享大量变量时,外部变量是很有用的。
然而在大多数情况下,对函数而言,通过形式参数进行通信比通过共享变量的方法更好,原因列举如下:
- 在程序维护期间,如果改变外部变量(比方说改变它的类型),那么将需要检查同一文件中的每个函数,以确认该变化如何对函数产生影响。
- 如果外部变量被赋了错误的值,可能很难确定出错的函数,就好像侦察大型聚会上的谋杀案时很难缩小嫌疑人范围一样。
- 很难在其他程序中复用依赖外部变量的函数。依赖外部变量的函数不是“独立的”。为了在另一个程序中使用该函数,必须带上此函数需要的外部变量。
许多
C程序员过于依赖外部变量。一个普遍的陋习是,在不同的函数中为不同的目的使用同一个外部变量。假设几个函数都需要变量i来控制for语句。一些程序员不是在使用变量i的每个函数中都声明它,而是在程序的顶部声明它,从而使得该变量对所有函数都是可见的。这种方式除了前面提到的几个缺点外,还会产生误导:以后阅读程序的人可能认为变量的使用彼此关联,而实际并非如此。
使用外部变量时,要确保它们都拥有有意义的名字。(局部变量不是总需要有意义的名字的,因为往往很难为for循环中的控制变量起一个比i更好的名字。)如果你发现为外部变量使用的名字就像i和temp一样,这可能意味着这些变量其实应该是局部变量。
请注意!把原本应该是局部变量的变量声明为外部变量可能导致一些令人厌烦的错误,请看下面这个例子:
//我们希望显示一个由星号组成的10×10的图形:int i; void print_one_row(void)
{ for (i = 1; i <= 10; i++) printf("*");
} void print_all_rows(void)
{ for (i = 1; i <= 10; i++) { print_one_row(); printf("\n"); }
}/*
print_all_rows函数不是显示10行星号,而是只显示1行。
在第一次调用print_one_row函数后返回时,i的值将为11。
然后,print_all_rows函数中的for语句对变量i进行自增并判定它是否小于或等于10。
因为判定条件不满足,所以循环终止,函数返回。
*/
为了获得更多关于外部变量的经验,现在编写一个简单的游戏程序。这个程序产生一个1~100的随机数,用户尝试用尽可能少的次数猜出这个数。
运行效果如下所示:
Guess the secret number between 1 and 100. A new number has been chosen.
Enter guess: 55
Too low; try again.
Enter guess: 65
Too high; try again.
Enter guess: 60
Too high; try again.
Enter guess: 58
You won in 4 guesses! Play again? (Y/N) y A new number has been chosen.
Enter guess: 78
Too high; try again.
Enter guess: 34
You won in 2 guesses! Play again? (Y/N) n
这个程序需要完成几个任务:初始化随机数生成器,选择神秘数,以及与用户交互直到选出正确数为止。如果编写独立的函数来处理每个任务,那么可能会得到下面的程序。
/*
guess.c(外部变量版本)
--Asks user to guess a hidden number
*/#include <stdio.h>
#include <stdlib.h>
#include <time.h> #define MAX_NUMBER 100 /* external variable */
int secret_number; /* prototypes */
void initialize_number_generator(void);
void choose_new_secret_number(void);
void read_guesses(void); int main(void)
{ char command; printf("Guess the secret number between 1 and %d.\n\n", MAX_NUMBER); initialize_number_generator(); do { choose_new_secret_number(); printf("A new number has been chosen.\n"); read_guesses(); printf("Play again? (Y/N) "); scanf(" %c", &command); printf("\n"); } while (command == 'y' || command == 'Y'); return 0;
} /************************************************************ * initialize_number_generator: Initializes the random * * number generator using * * the time of day. * ************************************************************/
void initialize_number_generator(void)
{ srand((unsigned)time(NULL));
} /************************************************************ * choose_new_secret_number: Randomly selects a number * * between 1 and MAX_NUMBER and * * stores it in secret_number. * ************************************************************/
void choose_new_secret_number(void)
{ secret_number = rand() % MAX_NUMBER + 1;
}/************************************************************ * read_guesses: Repeatedly reads user guesses and tells * * the user whether each guess is too low, * * too high, or correct. When the guess is * * correct, prints the total number of * * guesses and returns. * ************************************************************/
void read_guesses(void)
{ int guess, num_guesses = 0; for (;;) { num_guesses++; printf("Enter guess: "); scanf("%d", &guess); if(guess == secret_number) { printf("You won in %d guesses!\n\n", num_guesses); return; }else if(guess < secret_number) printf("Too low; try again.\n"); else printf("Too high; try again.\n"); }
}
对于随机数的生成,这次将缩放rand函数的返回值使其落在1~MAX_NUMBER范围内。虽然guess.c程序工作正常,但是它依赖一个外部变量。把变量secret_number外部化以便choose_new_secret_number函数和read_guesses函数都可以访问它。
如果对
choose_new_secret_number函数和read_guesses函数稍做改动,应该能把变量secret_number移入main函数中。现在我们将修改choose_new_secret_number函数以便函数返回新值,并将重写read_guesses函数以便变量secret_number可以作为参数传递给它,具体请看下面这个程序。
/*
guess2.c(形式参数版本)
--Asks user to guess a hidden number
*/#include <stdio.h>
#include <stdlib.h>
#include <time.h> #define MAX_NUMBER 100 /* prototypes */
void initialize_number_generator(void);
int new_secret_number(void); //修改
void read_guesses(int secret_number); //修改int main(void)
{ char command; int secret_number; //修改printf("Guess the secret number between 1 and %d.\n\n", MAX_NUMBER); initialize_number_generator(); do { secret_number = new_secret_number(); //修改printf("A new number has been chosen.\n"); read_guesses(secret_number); //修改printf("Play again? (Y/N) "); scanf(" %c", &command); printf("\n"); } while (command == 'y' || command == 'Y'); return 0;
} /************************************************************ * initialize_number_generator: Initializes the random * * number generator using * * the time of day. * ************************************************************/
void initialize_number_generator(void)
{ srand((unsigned)time(NULL));
} /************************************************************ * new_secret_number: Returns a randomly chosen number * * between 1 and MAX_NUMBER. * ************************************************************/
int new_secret_number(void) //修改
{ return rand() % MAX_NUMBER + 1;
} /************************************************************ * read_guesses: Repeatedly reads user guesses and tells * * the user whether each guess is too low, * * too high, or correct. When the guess is * * correct, prints the total number of * * guesses and returns. * ************************************************************/
void read_guesses(int secret_number) //修改
{ int guess, num_guesses = 0; for (;;) { num_guesses++; printf("Enter guess: "); scanf("%d", &guess); if (guess == secret_number) { printf("You won in %d guesses!\n\n", num_guesses); return; }else if(guess < secret_number) printf("Too low; try again.\n"); else printf("Too high; try again.\n"); }
}
10.3 程序块
5.2节遇到过复合语句,一个复合语句也是一个块(block),但块并非只有复合语句这一种形式。块也叫程序块。下面是程序块的示例:
if (i > j) { /* swap values of i and j */ int temp = i; i = j; j = temp;
}
这里,整个
if语句是一个程序块;if语句的每一个子句也是程序块。默认情况下,声明在程序块中的变量的存储期是自动的:进入程序块时为变量分配存储单元,退出程序块时收回分配的空间。变量具有块作用域,也就是说,不能在程序块外引用。
函数体是程序块。在需要临时使用变量时,函数体内的程序块也是非常有用的。在上面这个例子中,我们需要一个临时变量以便可以交换i和j的值。在程序块中放置临时变量有两个好处:(1)避免函数体起始位置的声明与只是临时使用的变量相混淆;(2)减少了名字冲突。在此例中,名字temp可以根据不同的目的用于同一函数中的其他地方,在程序块中声明的变量temp严格属于局部程序块。
C99允许在程序块的任何地方声明变量,就像允许在函数体内的任何地方声明变量一样。
10.4 作用域
在C程序中,相同的标识符可以有不同的含义。C语言的作用域规则使得程序员(和编译器)可以确定与程序中给定点相关的是哪种含义。
下面是最重要的作用域规则:当程序块内的声明命名一个标识符时,如果此标识符已经是可见的(因为此标识符拥有文件作用域,或者因为它已在某个程序块内声明),新的声明临时“隐藏”了旧的声明,标识符获得了新的含义。在程序块的末尾,标识符重新获得旧的含义。
思考下面这个(有点极端的)例子,例子中的标识符i有4种不同的含义。
- 在声明1中,
i是具有静态存储期和文件作用域的变量。 - 在声明2中,
i是具有块作用域的形式参数。 - 在声明3中,
i是具有块作用域的自动变量。 - 在声明4中,
i也是具有块作用域的自动变量。
int i; //声明1void f(int i){ //声明2i = 1;
}void g(void){int i = 2; //声明3if(i > 0){int i; //声明4i = 3;}i = 4;
}void h(void){i = 5;
}
一共使用了5次i。C语言的作用域规则允许确定每种情况中i的含义。
- 因为声明2隐藏了声明1,所以赋值
i = 1引用了声明2中的形式参数,而不是声明1中的变量。 - 因为声明3隐藏了声明1,而且声明2超出了作用域,所以判定
i > 0引用了声明3中的变量。 - 因为声明4隐藏了声明3,所以赋值
i = 3引用了声明4中的变量。 - 赋值
i = 4引用了声明3中的变量。声明4超出了作用域,所以不能引用。 - 赋值
i = 5引用了声明1中的变量。
10.5 构建C程序
我们已经看过构成
C程序的主要元素,现在应该为编排这些元素开发一套方法了。目前只考虑单个文件的程序,第15章会说明如何组织多个文件的程序。
迄今为止,已经知道程序可以包含:
include和#define这样的预处理指令;- 类型定义;
- 外部变量声明;
- 函数原型;
- 函数定义。
C语言对上述这些项的顺序要求极少:
- 执行到预处理指令所在的代码行时,预处理指令才会起作用;
- 类型名定义后才可以使用;
- 变量声明后才可以使用。
- 虽然
C语言对函数没有什么要求,但是这里强烈建议在第一次调用函数前要对每个函数进行定义或声明。( 至少C99要求我们这么做。)
为了遵守这些规则,这里有几个构建程序的方法。下面是一种可能的编排顺序:
#include指令;#define指令;- 类型定义;
- 外部变量的声明;
- 除
main函数之外的函数的原型; main函数的定义;- 其他函数的定义。
因为
#include指令带来的信息可能在程序中的好几个地方都需要,所以先放置这条指令是合理的。
#define指令创建宏,对这些宏的使用通常遍布整个程序。类型定义放置在外部变量声明的上面是合乎逻辑的,因为这些外部变量的声明可能会引用刚刚定义的类型名。
接下来,声明外部变量使得它们对于跟随在其后的所有函数都是可用的。在编译器看见原型之前调用函数,可能会产生问题,而此时声明除了
main函数以外的所有函数可以避免这些问题。这种方法也使得无论用什么顺序编排函数定义都是可能的。例如,根据函数名的字母顺序编排,或者把相关函数组合在一起进行编排。在其他函数前定义main函数使得阅读程序的人容易定位程序的起始点。
最后的建议:在每个函数定义前放盒型注释可以给出函数名、描述函数的目的、讨论每个式参数的含义、描述返回值(如果有的话)并罗列所有的副作用(如修改了外部变量的值)。
10.5.1 复杂程序:给一手牌分类
为了说明构建
C程序的方法,下面编写一个比前面的例子更复杂的程序——给一手牌分类。这个程序会对一手牌进行读取和分类。手中的每张牌都有花色(方块、梅花、红桃和黑桃)和点数(2、3、4、5、6、7、8、9、10、J、Q、K和A)。不允许使用王牌,并且假设A是最高的点数。程序将读取一手5张牌,然后把手中的牌分为下列某一类(列出的顺序从最好到最坏)。分类如下:
- 同花顺(即顺序相连又都是同花色)。
- 四张(
4张牌点数相同)。 - 葫芦(
3张牌是同样的点数,另外2张牌是同样的点数)。 - 同花(
5张牌是同花色的)。 - 顺子(
5张牌的点数顺序相连)。 - 三张(
3张牌的点数相同)。 - 两对。
- 对子(
2张牌的点数相同)。 - 其他牌(任何其他情况的牌)。
如果一手牌可分为两种或多种类别,程序将选择最好的一种。
为了便于输入,把牌的点数和花色简化如下(字母可以是大写,也可以是小写)。
- 点数:
2 3 4 5 6 7 8 9 t j q k a。 - 花色:
c d h s。
如果用户输入非法牌或者输入同一张牌两次,程序将忽略此牌,产生出错消息,然后要求输入另外一张牌。如果输入为0而不是一张牌,就会导致程序终止。
程序运行会话如下所示:
Enter a card: 2s
Enter a card: 5s
Enter a card: 4s
Enter a card: 3s
Enter a card: 6s
Straight flush Enter a card: 8c
Enter a card: as
Enter a card: 8c
Duplicate card; ignored.
Enter a card: 7c
Enter a card: ad
Enter a card: 3h
Pair Enter a card: 6s
Enter a card: d2
Bad card; ignored.
Enter a card: 2d
Enter a card: 9c
Enter a card: 4h
Enter a card: ts
High card Enter a card: 0
从上述程序的描述可以看出它有3个任务:
- 读入一手
5张牌; - 分析对子、顺子等情况;
- 显示一手牌的分类
把程序分为
3个函数,分别完成上述3个任务,即read_cards函数、analyze_hand函数和print_result函数。main函数只负责在无限循环中调用这些函数。这些函数需要共享大量的信息,所以让它们通过外部变量来进行交流。read_cards函数将与一手牌相关的信息存进几个外部变量中,然后analyze_hand函数将检查这些外部变量,把结果分类放在便于print_result函数显示的其他外部变量中。
/*
poker.c
--Classifies a poker hand
*/#include <stdbool.h> /* C99 only */
#include <stdio.h>
#include <stdlib.h> #define NUM_RANKS 13
#define NUM_SUITS 4
#define NUM_CARDS 5 /* external variables */
int num_in_rank[NUM_RANKS];
int num_in_suit[NUM_SUITS];
bool straight, flush, four, three;
int pairs; /* can be 0, 1, or 2 */ /* prototypes */
void read_cards(void);
void analyze_hand(void);
void print_result(void); /************************************************************ * main: Calls read_cards, analyze_hand, and print_result * * repeatedly. * ************************************************************/
int main(void)
{ for (;;) { read_cards(); analyze_hand(); print_result(); }
}/************************************************************ * read_cards: Reads the cards into the external * * variables num_in_rank and num_in_suit; * * checks for bad cards and duplicate cards. * ************************************************************/
void read_cards(void)
{ bool card_exists[NUM_RANKS][NUM_SUITS]; char ch, rank_ch, suit_ch; int rank, suit; bool bad_card; int cards_read = 0; for (rank = 0; rank < NUM_RANKS; rank++) { num_in_rank[rank] = 0; for (suit = 0; suit < NUM_SUITS; suit++) card_exists[rank][suit] = false; } for (suit = 0; suit < NUM_SUITS; suit++) num_in_suit[suit] = 0; while (cards_read < NUM_CARDS) { bad_card = false; printf("Enter a card: "); rank_ch = getchar(); switch (rank_ch) { case '0': exit(EXIT_SUCCESS); case '2': rank = 0; break; case '3': rank = 1; break; case '4': rank = 2; break; case '5': rank = 3; break; case '6': rank = 4; break; case '7': rank = 5; break; case '8': rank = 6; break; case '9': rank = 7; break; case 't': case 'T': rank = 8; break; case 'j': case 'J': rank = 9; break; case 'q': case 'Q': rank = 10; break; case 'k': case 'K': rank = 11; break; case 'a': case 'A': rank = 12; break; default: bad_card = true; } suit_ch = getchar(); switch (suit_ch) { case 'c': case 'C': suit = 0; break; case 'd': case 'D': suit = 1; break; case 'h': case 'H': suit = 2; break; case 's': case 'S': suit = 3; break; default: bad_card = true; } while ((ch = getchar()) != '\n') if (ch != ' ') bad_card = true; if (bad_card) printf("Bad card; ignored.\n"); else if (card_exists[rank][suit])printf("Duplicate card; ignored.\n"); else { num_in_rank[rank]++; num_in_suit[suit]++; card_exists[rank][suit] = true; cards_read++; } }
} /************************************************************ * analyze_hand: Determines whether the hand contains a * * straight, a flush, four-of-a-kind, * * and/or three-of-a-kind; determines the * * number of pairs; stores the results into * * the external variables straight, flush, * * four, three, and pairs. * ************************************************************/
void analyze_hand(void)
{ int num_consec = 0; int rank, suit; straight = false; flush = false; four = false; three = false; pairs = 0; /* check for flush */ for (suit = 0; suit < NUM_SUITS; suit++) if (num_in_suit[suit] == NUM_CARDS) flush = true; /* check for straight */ rank = 0; while (num_in_rank[rank] == 0) rank++; for (; rank < NUM_RANKS && num_in_rank[rank] > 0; rank++) num_consec++; if (num_consec == NUM_CARDS) { straight = true; return; } /* check for 4-of-a-kind, 3-of-a-kind, and pairs */ for (rank = 0; rank < NUM_RANKS; rank++) { if (num_in_rank[rank] == 4) four = true; if (num_in_rank[rank] == 3) three = true; if (num_in_rank[rank] == 2) pairs++; }
} /************************************************************ * print_result: prints the classification of the hand, * * based on the values of the external * * variables straight, flush, four, three, * * and pairs. * ************************************************************/
void print_result(void)
{ if (straight && flush) printf("Straight flush");else if (four) printf("Four of a kind"); else if (three && pairs == 1) printf("Full house"); else if (flush) printf("Flush"); else if (straight) printf("Straight"); else if (three) printf("Three of a kind"); else if (pairs == 2) printf("Two pairs"); else if (pairs == 1) printf("Pair"); else printf("High card"); printf("\n\n");
}
注意read_cards函数中exit函数的使用(第一个switch语句的分支'0')。因为exit函数具有在任何地方终止程序执行的能力,所以它对于此程序是十分方便的。
问与答
问1:具有静态存储期的局部变量会对递归函数产生什么影响?
答:当函数是递归函数时,每次调用它都会产生其自动变量的新副本。静态变量就不会发生这样的情况,相反,所有的函数调用都共享同一个静态变量。
问2:在下面的例子中,
j初始化为和i一样的值,但是有两个命名为i的变量:
int i = 1; void f(void)
{ int j = i; int i = 2; ...
}
这段代码是否合法?如果合法,j的初始值是1还是2?
答:代码是合法的。局部变量的作用域是从声明处开始的。因此,j的声明引用了名为i的外部变量。j的初始值是1。
写在最后
本文是博主阅读《C语言程序设计:现代方法(第2版·修订版)》时所作笔记,日后会持续更新后续章节笔记。欢迎各位大佬阅读学习,如有疑问请及时联系指正,希望对各位有所帮助,Thank you very much!
相关文章:
C现代方法(第10章)笔记——程序结构
文章目录 第10章 程序结构10.1 局部变量10.1.1 静态局部变量10.1.2 形式参数 10.2 外部变量10.2.1 示例:用外部变量实现栈10.2.2 外部变量的利与弊 10.3 程序块10.4 作用域10.5 构建C程序10.5.1 复杂程序:给一手牌分类 问与答写在最后 第10章 程序结构 …...
解密Web安全:Session、Cookie和Token的不解之谜
解密Web安全:Session、Cookie和Token的不解之谜 前言第一部分:什么是Session、Cookie和Token1. Session(会话):2. Cookie(HTTP Cookie):3. Token(令牌):比较: 第二部分&a…...
1016 部分A+B
#include<bits/stdc.h> using namespace std; int main(){string str1;string str2;int a,b;cin>>str1>>a>>str2>>b;int a10;int a20;for(auto t:str1){if(t-0a){a1a1*10a;}}for(auto t:str2){if(t-0b){a2a2*10b;}}cout<<a1a2; }...
搭建react项目
一、环境准备 1、安装node 官网下载安装:https://nodejs.org/en 注: npm5.2以后,安装node会自动安装npm和npx 2、安装webpack npm install -g webpack3、安装create-react-app npm install -g create-react-app二、创建react项目 1、初…...
Hive跨集群数据迁移过程
文章目录 环境数据迁移需求迁移过程记录 环境 Hive集群AHive集群B跳转机一台 数据迁移需求 本次迁移数据100G,15亿条,数据流转方向从集群A经过跳转机到集群B,通过HDFS拉取和重新建表导入的方式完成数据库迁移。 迁移过程记录 - 当前操作…...
中国移动启动算网大脑“天穹”全网试商用
10月12日,中国移动在2023全球合作伙伴大会主论坛正式启动算网大脑“天穹”全网试商用,全面开启算力网络2.0新征程,标志着中国移动算力网络迈向“融合统一”新阶段。 为落实国家“东数西算”战略,中国移动开创性提出算力网络新理念…...
apk和小程序渗透
apk和小程序域服务器通信使用的还是http协议,只是使用了加密。只要可以获取到http的请求报文,就可以回归到web渗透的层面。apk和小程序的渗透很复杂,涉及逆向时要进行脱壳,脱壳后反编译了,源代码没做加密就能直接逆向出…...
播放svga动画的时候 第一次加载资源,然后切换动画 会动画会重影
如果在切换 SVGA 动画的过程中,第一次加载时出现重影,但第二次及以后的切换没有重影,这可能是由于第一次加载时资源缓存不完整导致的。为了解决这个问题,你可以尝试以下方法: 1.在每次切换动画之前,预先加…...
如何实现前端音频和视频播放?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...
chatgpt 4V 识图功能
1.获取图片的sig和file_id 2e0edc6e489ed13a3f32f0dd87527d77.jpg是本地图片的名字 头部认证信息自己F12 抓取 1.获取图片的sighttps://chat.openai.com/backend-api/filesAuthorization:Bearer eyJhbGc****************5V-lztYwLb9hr6LP7g Cookie: **********************…...
展馆导览系统之AR互动式导航与展品语音讲解应用
一、项目背景 随着科技的进步和人们对于文化、艺术、历史等方面需求的提升,展馆在人们的生活中扮演着越来越重要的角色。然而,传统的展馆导览方式,如纸质导览、人工讲解等,已无法满足参观者的多元化需求。为了提升参观者的体验&a…...
Rust 中的String与所有权机制
文章目录 一、string二、所有权2.1 所有权与作用域2.2 对所有权的操作2.2.1 转移2.2.3 拷贝2.2.3 传递 2.3 引用2.3.1 借用2.3.2 可变引用 一、string 之前学习过 Rust 只有几种基础的数据类型,但是没有常用的字符串也就是String,今天来学习一下 String…...
多线程环境下如何安全的使用线性表, 队列, 哈希表
小王学习录 今日鸡汤安全使用ArrayList安全使用队列安全使用HashMap 今日鸡汤 安全使用ArrayList 使用synchronized锁或者reentrantLock锁使用CopyOnWriteArrayList(COW写时拷贝)类来代替ArrayList类. 多个线程对CopyOnWriteArrayList里面的ArrayList进行读操作, 不会发生线程…...
机器人SLAM与自主导航
机器人技术的迅猛发展,促使机器人逐渐走进了人们的生活,服务型室内移动机器人更是获得了广泛的关注。但室内机器人的普及还存在许多亟待解决的问题,定位与导航就是其中的关键问题之一。在这类问题的研究中,需要把握三个重点&#…...
Zookeeper集群 + Kafka集群的详细介绍与部署
文章目录 1. Zookeeper 概述1.1 简介1.2 Zookeeper的工作机制1.3 Zookeeper 主要特点1.4 Zookeeper 数据结构1.5 Zookeeper的相关应用场景1.5.1 统一命名服务1.5.2 统一配置管理1.5.3 统一集群管理1.5.4 服务器动态上下线1.5.5 软负载均衡 1.6 Zookeeper 选举机制1.6.1 第一次启…...
STP、堆叠与VRRP如何使用
✍ STP生成树用在哪里? ✍ STP和堆叠有什么区别? ✍ VRRP双网关热备份如何部署? --- 通过交换机组成网络是局域网,连接终端设备的交换机就是接入层交换机。 --- 如上组网结构单一,不需要网工。 容易发生单点故障&…...
Go 函数的健壮性、panic异常处理、defer 机制
Go 函数的健壮性、panic异常处理、defer 机制 文章目录 Go 函数的健壮性、panic异常处理、defer 机制一、函数健壮性的“三不要”原则1.1 原则一:不要相信任何外部输入的参数1.2 原则二:不要忽略任何一个错误1.3 原则三:不要假定异常不会发生…...
Maven的详细介绍(maven的全据配置以及idea中maven的配置)
maven的理解 Maven 是一个强大的项目管理和构建自动化工具,它通过抽象的项目对象模型(POM:Project Object Model)和构建生命周期模型(Project Lifecycle)来对项目及其构建过程进行管理(Dependency Management System),Maven 最大化的消除了构…...
Qt中Json的操作
在 Json的两种格式中介绍了Json的格式以及应用场景。由于这种数据格式与语言无关,下面介绍一下Json在Qt中的使用。 从Qt 5.0开始提供了对Json的支持,我们可以直接使用Qt提供的Json类进行数据的组织和解析。相关的类常用的主要有四个,具体如下: Json类介绍 QJsonDocument |…...
10. 机器学习-评测指标
Hi,你好。我是茶桁。 之前的课程中,我们学习了两个最重要的回归方法,一个线性回归,一个逻辑回归。也讲解了为什么学习机器学习要从逻辑回归和线性回归讲起。因为我们在解决问题的时候,有限选择简单的假设,越复杂的模型…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
Cursor AI 账号纯净度维护与高效注册指南
Cursor AI 账号纯净度维护与高效注册指南:解决限制问题的实战方案 风车无限免费邮箱系统网页端使用说明|快速获取邮箱|cursor|windsurf|augment 问题背景 在成功解决 Cursor 环境配置问题后,许多开发者仍面临账号纯净度不足导致的限制问题。无论使用 16…...
学习 Hooks【Plan - June - Week 2】
一、React API React 提供了丰富的核心 API,用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。 1. React 核心 API React.createElement(type, props, …children) 用于创建 React 元素,JSX 会被编译成该函数…...
day51 python CBAM注意力
目录 一、CBAM 模块简介 二、CBAM 模块的实现 (一)通道注意力模块 (二)空间注意力模块 (三)CBAM 模块的组合 三、CBAM 模块的特性 四、CBAM 模块在 CNN 中的应用 一、CBAM 模块简介 在之前的探索中…...
