实战项目精选
一、比特币地址验证
项目概况
编写一个以比特币地址为参数的程序,并检查该地址是否有效。比特币地址使用base58编码,该Base58是用于比特币中使用的一种独特的编码方式,主要用于产生比特币的钱包地址。相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+"和"/"符号。
通过这种编码,比特币地址可编码25个字节:
1、第一个字节是版本号,此任务的版本号为零;
3、最后四个字节是校验和检查。它们是前21个字节的双sha-256摘要的前四个字节。
要检查比特币地址,必须读取前21个字节,计算校验和,并检查它是否与最后4个字节对应。
程序可以返回布尔值,也可以在无效时引发异常。
项目源码
#include <stdio.h>
#include <string.h>
//用来加密的头文件
#include <openssl/sha.h>
//coin_err表示最终结果
const char *coin_err;
//对coin_err进行赋值的函数
#define bail(s) { coin_err = s; return 0; }
//对字符串base58编码
int unbase58(const char *s, unsigned char *out){
//定义一个常量字符串,包含所有的合法字符
static const char *tmpl = "123456789" "ABCDEFGHJKLMNPQRSTUVWXYZ"
"abcdefghijkmnopqrstuvwxyz";
//定义循环变量
int i,j, c;
//用来记录匹配字符串的地址
const char *p;
//对输出字符串out进行初始化
memset(out, 0, 25);
//对原始字符串进行遍历
for (i = 0; s[i]; i++) {
//查找给定字符的第一次匹配的地方
if (!(p = strchr(tmpl, s[i])))
//如果没有合法字符,代表是一个bad地址
bail("bad char");
//c表示在进制转换时的进位
c = p - tmpl;
//用来处理表示58进制结果的out字符串,编码25个字节
for (j = 25; j--; )
//采用58进制
c += 58 * out[j];
//256进制的计算过程
out[j] = c % 256;
c /= 256;
}
//地址太长,所以编码失败,返回0
if (c) bail("address too long");
}
return 1;
}
//判断是否为58进制编码
int valid(const char *s) {
//dec表示经过58进制编码后的字符串
unsigned char dec[32], d1[SHA256_DIGEST_LENGTH], d2[SHA256_DIGEST_LENGTH];
//表示的状态:是否为有效地址
coin_err = "";
//如果不符合58进制,直接返回0
if (!unbase58(s, dec)) return 0;
//双哈希,采用两次
SHA256加密哈希算法 SHA256(SHA256(dec, 21, d1), SHA256_DIGEST_LENGTH, d2);
//比较dec从第21个字符开始后的与d2的前四个字符
if (memcmp(dec + 21, d2, 4))
//如果不相等就是属于不合法的
bail("bad digest");
//正常返回,即是该地址有效
return 1;
}
int main (void) {
//给定需要判断的字符串地址
const char *s[] = {
"1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9",
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i",
"1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9",
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I",
0 };
//循环变量
int i;
//对每个字符串进行判断
for (i = 0; s[i]; i++) {
//计算该地址是否有效
int status = valid(s[i]);
//输出结果
printf("%s: %s\n", s[i], status ? "Ok" : coin_err);
}
return 0;
}
运行结果
二、生成随机国际象棋
项目概况
这项任务的目的是以FEN格式生成一个随机的棋盘。位置不一定要符合现实情况,甚至不一定符合平衡性,但必须遵守以下规则:
1、每种颜色只有一个国王(一个黑国王和一个白国王);
2、两个国王不能被安排在相邻的格子上;
3、升变方格上不能有兵(第八条横线上没有白色兵,第一条横线上没有黑色兵);
4、包括国王在内,每种颜色最多可放置32个。双方之间没有数量平衡的要求;挑选棋子不必遵守常规的国际象棋规则:可以有五个马,二十个车……只要总个数不超过三十二。
5、现在轮到白色棋子的回合,假定双方都失去了易位,不能使用吃过路兵(En passant)的移动方式(因此,FEN应以w--01结束)
最终显示如下所示:
(注:由于是随机值,所以每次运行结果可能不一样)
项目源码
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
//进行宏定义,即TRUE表示1
#define TRUE 1
#define FALSE 0
//类型装换由于C语言中没有bool类型,所以用int代替
typedef int bool;
//生成的棋盘格子
char grid[8][8];
//生成King的坐标
void placeKings() {
int r1, r2, c1, c2;
//死循环的
for (;;) {
//随机生成两个坐标,因为是8*8的方格所以横纵坐标都对8取模
//第一个king的坐标
r1 = rand() % 8;
c1 = rand() % 8;
//第二个king的坐标
r2 = rand() % 8;
c2 = rand() % 8;
if (r1 != r2 && abs(r1 - r2) > 1 && abs(c1 - c2) > 1) {
//如果符合条件那么赋予两个King,为了区分开,一个大写K,一个小写k
grid[r1][c1] = 'K';
grid[r2][c2] = 'k';
//直接return,函数结束
return;
}
}
}
//随机生成位置棋子
void placePieces(const char *pieces, bool isPawn) {
int n, r, c;
int numToPlace = rand() % strlen(pieces);
//循环刚才取得的随机数次
for (n = 0; n < numToPlace; ++n) {
//随机生成一个坐标,对8取模
do {
r = rand() % 8;
c = rand() % 8;
}
//如果当前位置上没有棋子为空且升变方格上不能是兵
while (grid[r][c] != 0 || (isPawn && (r == 7 || r == 0)));
//做给定(r,c)坐标位置上的棋子
grid[r][c] = pieces[n];
}
}
//转化为FEN格式
void toFen() {
char fen[80], ch;
/**
(r,c)表示棋盘坐标
countEmpty:空格子的个数
index:fen格式串的下标索引
**/
int r, c, countEmpty = 0, index = 0;
//8*8的棋盘
for (r = 0; r < 8; ++r) {
for (c = 0; c < 8; ++c) {
//取得当前坐标的棋子
ch = grid[r][c];
//如果ch是0的话,说明是空格子,打印.
printf("%2c ", ch == 0 ? '.' : ch);
//如果ch是空格子,那么countEmpty计数+1
if (ch == 0) {
countEmpty++;
}
else {
if (countEmpty > 0) {
//fen字符串的当前位置 置为0,因为0的ascii码是48
fen[index++] = countEmpty + 48;
//然后重新将countEmpty置为0
countEmpty = 0;
}
//将ch赋值为fen的当前字符
fen[index++] = ch;
}
}
//在每一行结束在处理一遍,跟上面代码相同
if (countEmpty > 0) {
fen[index++] = countEmpty + 48;
countEmpty = 0;
}
//fen字符串设置为
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专刊由牛客官方团队打造,从一个入门者的角度写下这篇C语言自学指南,内容丰富详实,每一道例题也都是精挑细选,不管是C语言小白抑或是“老司机”,都能在本刊中有所收获。 本专刊购买后即可解锁所有章节,故不可以退换哦~

查看1道真题和解析