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,你好。我是茶桁。 之前的课程中,我们学习了两个最重要的回归方法,一个线性回归,一个逻辑回归。也讲解了为什么学习机器学习要从逻辑回归和线性回归讲起。因为我们在解决问题的时候,有限选择简单的假设,越复杂的模型…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...