需求
目前需要实现生成8-12位密码,密码要求至少包含一位数字,一位大写字母,一位小写字母,一位特殊字符。特殊字符仅包含(“@”,“!”,“_”)
需求分析
可知我们需要1个功能,1.生成默认随机密码 ,
源码
#include <stdio.h>
#include <stdlib.h>
// rand(),srand()
#include <string.h>
#include <time.h>
#define MIN_PASSWORD_LENGTH 8
#define MAX_PASSWORD_LENGTH 12
void shuffle (char *password)
{
int i,j;
int length = strlen(password);
for (i = 0; i < length; i++)
{
j = rand() % length;
char temp = password[i];
password[i] = password[j];
password[j] = temp;
}
}
void generatePassword(char *password, int length)
{
const char *numbers = "0123456789";
const char *upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *lowerChars = "abcdefghijklmnopqrstuvwxyz";
const char *specials = "@!_";
int allLength = strlen(numbers) + strlen(upperChars) + strlen(lowerChars) + strlen(specials) + 1;
char charall[allLength];
strcpy(charall,numbers);
strcat(charall,upperChars);
strcat(charall,lowerChars);
strcat(charall,specials);
int i, charPos;
// 初始化密码数组
for (i = 0; i < length; i++)
{
password[i] = 0;
}
// 确保密码至少包含一个数字、一个大写字母、一个小写字母和一个特殊字符
password[0] = numbers[rand() % strlen(numbers)];
password[1] = upperChars[rand() % strlen(upperChars)];
password[2] = lowerChars[rand() % strlen(lowerChars)];
password[3] = specials[rand() % strlen(specials)];
for (i = 4; i < length; i++)
{
charPos = rand() % (strlen(charall));
password[i] = charall[charPos];
}
// 打乱密码中的字符顺序,以确保随机性
shuffle(password);
password[length] = '\0';
}
int generateLength(int min,int max)
{
return rand() % (max - min + 1) + min;
}
int main()
{
int i;
char password[MAX_PASSWORD_LENGTH+1];
srand(time(NULL));
for (i = 1; i <= 50; i++)
{
int length = generateLength(MIN_PASSWORD_LENGTH,MAX_PASSWORD_LENGTH);
generatePassword(password, length);
printf("Number : %d , the length of pwd : %d, Generated Password: %s\n", i, length, password);
}
return 0;
}
运行结果
代码分析
初始化随机数生成器
函数原型
void srand(unsigned int seed);
srand(time(NULL));
这一行是用来初始化随机数生成器的。srand 函数是 C 标准库"stdlib.h"中的一个函数,它设置了随机数生成器的种子值。种子值通常是当前时间,由 time 函数返回,这样每次运行程序时生成的随机数序列都会不同。
这个种子值随后被 rand() 函数用来生成随机数序列。种子值是随机数生成算法的起点,不同的种子值通常会导致生成不同的随机数序列。
随机数序列生成器
rand() 函数是 C 语言标准库中的一个函数,用于生成伪随机数。这个函数返回一个介于 0 和 RAND_MAX 之间的整数,其中 RAND_MAX 是一个定义在 <stdlib.h> 头文件中的常量,这个值可能因编译器和平台而异。
字符串处理
声明一个char型数组来存储最终生成的随机密码,这里有个疑问是这个数组的长度为什么是MAX_PASSWORD_LENGTH + 1,如果仅仅为了存储密码的话,最多MAX_PASSWORD_LENGTH长度不就够了吗?
char password[MAX_PASSWORD_LENGTH + 1];
解惑
在 C 语言中,当你定义一个字符数组如 char password[MAX_PASSWORD_LENGTH + 1]; 时,这里的 +1 是为了确保数组有一个额外的空间来存储字符串的结束标志 ‘\0’,也就是空字符(null terminator)。这个空字符用于表示字符串的结束,在 C 语言中是处理字符串时的一个标准做法。
为什么需要额外的一位
考虑以下两个主要原因:
字符串结束标志
在 C 语言中,字符串以空字符 ‘\0’ 结尾。这个字符告诉函数(如 printf),字符串在哪里结束。如果不包含这个结束字符,字符串处理函数可能继续读取内存直到遇到一个随机的零字节,这可能导致未定义的行为,包括安全漏洞。
数组越界保护
在数组的末尾留出一个额外的空间可以作为缓冲,防止在字符串操作中意外写入数组界限之外,这也是一种常见的安全措施。
填充字符
不重复
for (i = 4; i < length; i++) {
do {
charPos = rand() % (strlen(numbers) + strlen(upperChars) + strlen(lowerChars) + strlen(specials));
if (charPos < strlen(numbers)) {
password[i] = numbers[charPos];
} else if (charPos < strlen(numbers) + strlen(upperChars)) {
password[i] = upperChars[charPos - strlen(numbers)];
} else if (charPos < strlen(numbers) + strlen(upperChars) + strlen(lowerChars)) {
password[i] = lowerChars[charPos - strlen(numbers) - strlen(upperChars)];
} else {
password[i] = specials[charPos - strlen(numbers) - strlen(upperChars) - strlen(lowerChars)];
}
} while (used[(unsigned char)password[i]]); // 确保不重复
used[(unsigned char)password[i]] = 1;
}
可重复
for (i = 4; i < length; i++)
{
charPos = rand() % (strlen(numbers) + strlen(upperChars) + strlen(lowerChars) + strlen(specials));
if (charPos < strlen(numbers))
{
password[i] = numbers[charPos];
}
else if (charPos < strlen(numbers) + strlen(upperChars))
{
password[i] = upperChars[charPos - strlen(numbers)];
}
else if (charPos < strlen(numbers) + strlen(upperChars) + strlen(lowerChars))
{
password[i] = lowerChars[charPos - strlen(numbers) - strlen(upperChars)];
}
else
{
password[i] = specials[charPos - strlen(numbers) - strlen(upperChars) - strlen(lowerChars)];
}
}
为什么使用unsigned char而不是unsigned int
while (used[(unsigned char)password[i]]); // 确保不重复
used[(unsigned char)password[i]] = 1;
在 C 语言中,使用 (unsigned char)password[i] 而不是 unsigned int 对字符进行类型转换,主要是为了确保索引值落在数组的有效范围内,并且提高代码的可移植性和安全性。这里有几个原因为什么使用 unsigned char 比 unsigned int 更合适:
数组索引的安全性
在 C 语言中,数组索引应该是无符号字符或整数类型。使用 unsigned char 可以确保即使字符的 ASCII 值非常大(接近 UCHAR_MAX,即 unsigned char 类型的最大值),转换后的值仍然可以作为数组索引使用,而不会引起索引越界。
防止数据类型溢出
如果使用 unsigned int,而字符的实际值超过了 unsigned int 能表示的范围,那么会发生数据类型溢出,这可能导致不可预测的行为。使用 unsigned char 可以避免这种情况,因为它的范围较小,足以表示任何可能的字符值。
节省内存
unsigned char 通常只占用一个字节,而 unsigned int 可能占用四个字节(取决于平台)。在处理大量数据时,使用 unsigned char 可以节省内存空间。
可移植性
不同的编译器和平台可能对数据类型的大小有不同的定义。使用 unsigned char 可以提高代码的可移植性,因为它的大小是一致的(总是一个字节),而 unsigned int 的大小可能会有所不同。
语义明确
使用 unsigned char 作为数组索引明确表达了程序员的意图,即这个值是用来索引数组的,而不是用作其他计算。
无符号字符(unsigned char)为什么可以作为数组索引
大小和表示范围
unsigned char 类型通常占用一个字节(8位),其值域是从 0 到 255。这意味着它有足够的空间来表示数组索引,即使是在大多数情况下,数组的索引也不会超过这个范围。
类型安全性
在 C 语言中,数组索引在底层实际上是以整数形式处理的。当你使用一个变量作为数组索引时,编译器会将其转换为整数类型。unsigned char 作为一个无符号整数类型,可以安全地转换为一个更大的整数类型(如 int),而不会引起负值或符号扩展的问题。
性能
使用 unsigned char 作为索引可以提供更好的性能,因为它的大小较小,处理起来更快。在某些情况下,使用较小的数据类型可以减少内存使用和提高缓存效率。
避免负值
由于 unsigned char 是无符号的,它不会表示负值。这可以避免在某些情况下意外地将负值用作数组索引,这可能会导致未定义行为或程序崩溃。
符合标准
C 语言标准允许使用任何整数类型的值作为数组索引,包括 unsigned char。这意味着使用 unsigned char 作为数组索引是符合标准的,并且可以在所有遵循标准的 C 语言编译器上工作。
实际应用
在实际编程中,使用 unsigned char 作为数组索引可以帮助确保索引值的合法性和安全性,特别是在处理字符数据和字符串时。例如,当你遍历一个字符串并使用字符数组来跟踪字符出现的情况时,使用 unsigned char 可以确保索引值始终有效。
strlen()
size_t strlen(const char *str);
char *和char[]有什么区别
char *upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char upperChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
存储方式:
char *upperChars = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”;:这里 upperChars 是一个指向字符的指针,它指向一个静态分配的字符串字面量。字符串字面量是由编译器在程序的只读数据段中自动分配的,因此不能被修改。
char upperChars[] = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”;:这里 upperChars 是一个字符数组,它在初始化时会将字符串字面量的内容复制到数组中。这意味着数组的内容存储在程序的可写内存区域,可以被修改。
内存分配:
指针声明不涉及数组的大小分配,它只是指向一个已经存在的字符串。
数组声明会根据初始化时提供的字符串长度(包括结尾的空字符 \0)在栈上分配内存。
修改内容:
通过指针 upperChars 指向的字符串是不可修改的,尝试修改它的任何字符都会导致未定义行为,通常是程序崩溃。
数组 upperChars 的内容是可以修改的,你可以更改数组中的任何字符。
数组大小:
对于指针,你不能得到它所指向的字符串的长度,除非你使用 strlen 函数。
对于数组,编译器知道数组的大小,因此你可以使用 sizeof(upperChars) 来得到字符串的长度,包括结尾的空字符。
用途:
指针通常用于指向字符串常量或动态分配的字符串。
数组通常用于创建可修改的字符串副本或在栈上存储固定长度的字符串。