2020/11/4更

沒想到這麼多人點贊收藏的,繼續分享一個,編譯期解析執行BrainFuck代碼,生成"Hello World!"字元串並列印出來:

怎麼使C++用最複雜的方法打hello world??

www.zhihu.com圖標

-----------原文-------------

編譯時快速排序。

template &
struct TypeList {
using type = TypeList&;

template &
using append = TypeList&;

template & typename T&>
using exportTo = T&;
};

template& typename P,
typename S = TypeList&,
typename R = TypeList&&>
struct Partition {
struct type {
using satisfied = S;
using rest = R;
};
};

template& class P&>
using Partition_t = typename Partition&::type;

template& typename P, typename S, typename R&>
struct Partition&, P, S, R&>: std::conditional_t&

::value,
Partition&, P, typename S::template append&, R&>,
Partition&, P, S, typename R::template append&&>&> {};

template&
using Concat_t = IN2::template exportTo&;

template& class CMP&>
struct QuickSort: TypeList& {};

template& class CMP&>
using QuickSort_t = typename QuickSort&::type;

template& class CMP, typename H, typename ...Ts&>
class QuickSort&, CMP&> {
template& using LT = CMP&;
using P = Partition_t&, LT&>;
using SmallerSorted = QuickSort_t&;
using BiggerSorted = QuickSort_t&;
public:
using type = Concat_t&,
BiggerSorted&>;
};

可以用在編譯時圖的拓撲排序中,更多細節請見:

https://zhuanlan.zhihu.com/p/185034212?

zhuanlan.zhihu.com圖標

更多請關注我的專欄:

魅力C++?

www.zhihu.com圖標

純C語言實現 生成二維碼

Update:今晚更新下原理.

二維碼圖片會被知乎自動識別,上動圖。

寫這段代碼的原因是看到用c語言代碼可以形成那些有趣的圖? 這個問題,想著怎麼才能用有限的代碼畫出多樣的世界呢,畫出二維碼不就好了嗎!

於是打開搜索引擎,從二維碼原理學起,耗時三小時完成這個生成QR二維碼的程序。很多細節都是一把梭的,成功掃描出HELLO WORLD的那一刻,心裡還是很激動的。

有人想學原理的話留個言,我之後更新一下。

===== 分割線 ,以下為原理

評論區有很多小夥伴要求補充原理,我就在這裡簡單講一下吧,由於網上類似的文章比較多,但是大都是轉載或者翻譯官方文檔,並沒有指出真正實現應該怎麼做,所以下文著重講生成最簡單的QR碼需要具備的知識以及如何真正用代碼實現。

實現整個代碼的過程可以分為,準備數據碼,準備糾錯碼,輸出二維碼三個部分,下面會通過"HELLO WORLD"這個例子展開講解每個部分。

0.準備數據碼

0.1.確定糾錯等級

糾錯等級分為 L, M, Q, H,越高的糾錯等級擁有更強的恢複數據的能力,更高級別的錯誤校正需要更多的位元組,因此更高級別的錯誤校正將必須具有更大的QR碼。 這裡我們選擇M糾錯級別。

0.2.確定數據最小版本

不同大小的QR碼稱為版本。二維碼目前有四十個版本。最小的版本是版本1,大小為21像素x 21像素。版本2為25像素乘25像素。最大的版本是40版,大小為177 x 177像素。每個版本比以前的版本大4個像素。每個版本具有最大容量,具體取決於使用的模式。同時對於糾錯碼的選擇也會對版本的最大容量有影響,具體的官方給出了一張表可以查看,容量表。在我是實現代碼時,目標是簡單實現,所以選擇了最小的21x21版本。

0.3.模式指示器

模式指示器就是說使用什麼編碼形式,編碼形式主要有數值模式、字母數字模式、位元組模式、漢字模式、ECI模式。我為了掃描出HELLO WORLD,選擇了比較簡單的字母數字模式,關於模式與對應的編碼可以參考下表。

模式名稱 模式編碼
數值模式 0001
字母數字模式 0010
位元組模式 0100
漢字模式 1000
ECI模式 0111

所以這裡我們的模式編碼為0010,記住這個數字,這是最終二維碼黑白塊的一部分~

0.4.字元長度指示器

這一步是展示你的字元是多長的,對於不同的版本需要把字元長度轉成不同的二進位編碼長度,可以參考下面這張表。

版本模式 數值模式 字母數字模式 位元組模式 日文模式
版本1 - 9 10位 9位 8位 8位
版本10 - 26 12位 11位 16位 10位
版本27-40 14位 13位 16位 12位

上文提到我們使用的是字母數字模式,"HELLO WORLD" 的長度為11,轉成二進位為1011,我們需要補充位數到九位,得到000001011

把上一步得到的模式指示器編碼0010與字元長度指示器編碼接到一起,得到0010 000001011。

0.5.字元編碼

上文中提到我們使用的是字母數字編碼模式,於是我們參考字母數字編碼規範進行字元編碼,首先根據字元索引表查到每個字元對應的索引值,將整個字元串從左到右兩兩分組,每組中第一個字元索引值乘以 45 加上第二個字元索引值,將結果轉化為 11 位的二進位數,不足 11 位在左側補 0 以達到長度。如果字元串長度為奇數,將最後的單身狗字元轉換成6位二進位。

最終,"HELLO WORLD"可以得到以下字元編碼值:

HE -&> 17*45 + 14 -&> 779 -&> 01100001011

LL -&> 01111000110

O(空格) -&>10001011100

WO -&> 10110111000

RL -&> 10011010100

D -&> 001101

將以上內容拼接起來,我們得到61位字元編碼,根據QR二維碼規範,版本1-M的二維碼需要128位的數據編碼,目前我們已經有 4 位編碼指示符(0010),9 位字元計數符(000001011) ,和 61 位的字元編碼,共計74位,還差54位。根據規則,當長度不達到128位時,我們需要做如下流程。

如果相差位數大於等於4位,那麼增添一個4位的終止符, 0000,我們距離128位還差54位,顯然是大於4的,於是加上4位終止符,長度來到78位。

如果增添終止符之後仍然達不到128位,那麼要先將整個數據編碼補充0,直到長度為8的倍數,於是我們加上 00,長度來到80位。

如果當前長度仍然達不到128位,則在整個字元串之後交替添加1110110000010001直到字元串長度達到128位,我們依次添加上述字元串,得到最終的數據編碼:

00100000 01011011 00001011 01111000 11010001 01110010 11011100 01001101 01000011 01000000 11101100 00010001 11101100 00010001 11101100 00010001

截止到目前,相信各位只要跟著一步步算下來,應該都是可以理解的,不理解的可以評論區評論下我的置頂評論,我會一一回復,上述的流程編碼起來也就是一些字元串處理,相信各位都可以隨便寫完,不妨試一試,看看能不能得到一樣的數據碼。

1.準備糾錯碼

我們目前已經拿到了128位數據碼,還差一步就可以開始畫圖了,就是糾錯碼。糾錯碼生成的步驟是比較繁瑣的,查閱比較多的中文博客大部分都是跳過的...這裡我會展開講一下版本1-M糾錯碼的生成細節,如果各位覺得繁瑣可以直接借鑒我的GetErrorCode函數。

1.0 前置知識

多項式除法

Galois Field

用log及反log的乘法運算

(先上班了,晚上接著更~)

===== 分割線 , 以下為代碼

#include &
#include &
#include &
#include &

#define QR_SIZE 21
#define PRINT_SIZE 31
#define GF_SIZE 256
#define DATACODE_SIZE 129
#define DEBUG 0

int mat[QR_SIZE][QR_SIZE];
int vis[QR_SIZE][QR_SIZE];
int output[PRINT_SIZE][PRINT_SIZE];
int GF256[GF_SIZE], RGF256[GF_SIZE];

int posMat[9][9];
int errorCodeNum[12];

char dataCode[DATACODE_SIZE];
char errorCodeChar[DATACODE_SIZE];
const char blue[] = "101010000010010";
const char dict[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
const int codePoly[] = {0, 251, 67, 46, 61, 118, 70, 64, 94, 32, 45};
const int bluePos[2][15][2] = {
8, 0, 8, 1, 8, 2, 8, 3, 8, 4, 8, 5, 8, 7, 8, 8, 7, 8, 5, 8, 4, 8, 3, 8, 2, 8, 1, 8, 0, 8,
20, 8, 19, 8, 18, 8, 17, 8, 16, 8, 15, 8, 14, 8, 8, 13, 8, 14, 8, 15, 8, 16, 8, 17, 8, 18, 8, 19, 8, 20
};

void PrepareDingWei() {
for (int i = 1; i &< 8; i++) { for (int j = 1; j &< 8; j++) { if (i == 1 || j == 1 || i == 7 || j == 7) { posMat[i][j] = 1; } } } for (int i = 3; i &<= 5; i++) { for (int j = 3; j &<= 5; j++) { posMat[i][j] = 1; } } } void DrawDingWei(int x1, int y1, int x2, int y2) { for (int i = x1, i2 = x2; i &< 9; i++, i2++) { if (i2 &>= QR_SIZE) continue;
for (int j = y1, j2 = y2; j &< 9; j++, j2++) { if (j2 &>= QR_SIZE) continue;
mat[i2][j2] = posMat[i][j];
vis[i2][j2] = 1;
}
}
}

// 最終的列印,顯示構造二維碼矩陣,然後用一個大的矩陣包住小矩陣
void Print() {
int sum = 0;
for (int i = 0; i &< QR_SIZE; i++) { for (int j = 0; j &< QR_SIZE; j++) { if (vis[i][j]) { sum++; } } } int id = 0; int x = QR_SIZE - 1, y = QR_SIZE - 1, flag = 1; for (int j = QR_SIZE - 1; j &>= 0; j--) {
int pos = 0;
if (flag == 1) {
for (;;) {
mat[x][y] = dataCode[id++] - 0;
if (pos == 0) {
y--;
} else {
if (x == 0) {
y--;
flag = 0;
break;
} else if (vis[x - 1][y + 1]) {
if (!vis[x - 2][y + 1]) {
x -= 2;
y += 1;
} else {
y--;
flag = 0;
break;
}
} else {
x--;
y++;
}
}
pos = 1 - pos;
}
} else {
for (;;) {
mat[x][y] = dataCode[id++] - 0;
if (pos == 0) {
y--;
} else {
if (x == QR_SIZE - 1) {
if (y == 9) {
x = 12;
y--;
flag = 1;
break;
} else {
y--;
flag = 1;
break;
}
} else if (vis[x + 1][y + 1]) {
if (!vis[x + 2][y + 1]) {
x += 2;
y += 1;
} else {
flag = 1;
break;
}
} else {
x += 1;
y += 1;
}
}
pos = 1 - pos;
}
}
if (j == 8) j -= 2, y--;
else j--;
}
for (int i = 0; i &< QR_SIZE; i++) { for (int j = 0; j &< QR_SIZE; j++) { if (!vis[i][j] (i + j) % 2 == 0) { mat[i][j] = 1 - mat[i][j]; } } } char black = ; char white = 219; for (int i = 0; i &< PRINT_SIZE; i++) { for (int j = 0; j &< PRINT_SIZE; j++) { output[i][j] = 0; } } for (int i = 0; i &< QR_SIZE; i++) { for (int j = 0; j &< QR_SIZE; j++) { output[i + 5][j + 5] = mat[i][j]; } } for (int i = 0; i &< PRINT_SIZE; i++) { for (int j = 0; j &< PRINT_SIZE; j++) { if (output[i][j]) { printf("%c%c", black, black); } else { printf("%c%c", white, white); } } printf(" "); } } // 畫出二維碼中固定的部分,例如定位符和掩碼 void DrawBackground() { // 準備定位符的一份矩陣,然後直接copy三份到左上,右上,右下 PrepareDingWei(); DrawDingWei(1, 1, 0, 0); DrawDingWei(1, 0, 0, 13); DrawDingWei(0, 1, 13, 0); // 開始畫背景中的掩碼 { for (int i = 0; i &< 2; i++) { for (int j = 0; j &< 15; j++) { vis[bluePos[i][j][0]][bluePos[i][j][1]] = 1; mat[bluePos[i][j][0]][bluePos[i][j][1]] = blue[j] - 0; } } for (int i = 8; i &<= 12; i++) { mat[6][i] = !((i + 6) 1); vis[6][i] = 1; mat[i][6] = !((i + 6) 1); vis[i][6] = 1; } } } // 查找字元c所對應的權值 int FindIndex(char c) { int len = strlen(dict); for (int i = 0; i &< len; i++) { if (dict[i] == c) { return i; } } return -1; } // 得到數值num所對應的len長度的二進位編碼 char *GetLenBinString(int num, int len) { char *rtn = (char *) malloc(len); rtn[len] = ; int idx = len - 1; while (idx &>= 0) {
rtn[idx] = (num % 2) + 0;
num /= 2;
idx--;
}
return rtn;
}

// 得到數據的數據碼 數據碼 + 補碼
void GetDataCode(char *text) {
int len = strlen(text);
char headCode[14] = "0010";
char *textLen = GetLenBinString(strlen(text), 9);
strcat(headCode, textLen);
strcat(dataCode, headCode);
for (int i = 0; i &< len - 1; i += 2) { char *result = GetLenBinString(FindIndex(text[i]) * 45 + FindIndex(text[i + 1]), 11); strcat(dataCode, result); free(result); } if (len 1) { char *result = GetLenBinString(FindIndex(text[len - 1]), 6); strcat(dataCode, result); free(result); } if (DATACODE_SIZE - strlen(dataCode) &>= 4) {
strcat(dataCode, "0000");
}
int currentLen = strlen(dataCode);
while (currentLen % 8 != 0) {
strcat(dataCode, "0");
currentLen++;
}
char appendCode1[9] = "11101100";
char appendCode2[9] = "00010001";
while (currentLen &< 128) { strcat(dataCode, appendCode1); currentLen += 8; if (currentLen &< 128) { strcat(dataCode, appendCode2); currentLen += 8; } } } void PrepareGF256() { GF256[0] = 1; RGF256[1] = 0; for (int i = 1; i &< GF_SIZE; i++) { GF256[i] = GF256[i - 1] * 2; if (GF256[i] &> 255) {
GF256[i] = (GF256[i] ^ 285);
}
RGF256[GF256[i]] = i;
}
}

// Reed-Solomon糾錯碼 「HELLO WORLD」 -&> 196 35 39 119 235 215 231 226 93 23
// 演算法細節參考: https://www.thonky.com/qr-code-tutorial/error-correction-coding
void GetErrorCode() {
int infoPolyCoefficient[30];
for (int i = 0; i &<= 25; i++) { infoPolyCoefficient[i] = 0; } for (int i = 0; i &< strlen(dataCode); i += 8) { int num = 0; for (int j = i; j &< i + 8; j++) { num = num * 2 + (int) (dataCode[j] - 0); } infoPolyCoefficient[i / 8] = num; } int GenPolyCoefficient[15]; for (int i = 0; i &<= 10; i++) { GenPolyCoefficient[i] = codePoly[i]; } for (int i = 0; i &< 16; i++) { int mul = RGF256[infoPolyCoefficient[i]]; for (int j = i; j &<= i + 10; j++) { infoPolyCoefficient[j] = (infoPolyCoefficient[j] ^ GF256[(GenPolyCoefficient[j - i] + mul) % 255]); } } for (int j = 0; j &< 10; j++) { errorCodeNum[j] = infoPolyCoefficient[j + 16]; } if (DEBUG) { for (int i = 0; i &< 10; i ++) { printf("%d ",errorCodeNum[i]); } printf(" "); } for (int i = 0; i &< 10; i++) { char *result = GetLenBinString(errorCodeNum[i], 8); strcat(errorCodeChar, result); free(result); } } int main(int argc, char **argv) { SetConsoleOutputCP(437); char *text = argv[1]; DrawBackground(); GetDataCode(text); PrepareGF256(); GetErrorCode(); strcat(dataCode, errorCodeChar); if (DEBUG) { printf("ErrorCode : %s len : %llu ",dataCode, strlen(errorCodeChar)); printf("DataCode : %s len : %llu ",dataCode, strlen(dataCode)); } Print(); return 0; }

我是biubiubiu,一隻認真寫回答的程序猿!


當初研究狀態機的時候用一堆模板什麼的堆了個 XML 解析器出來,真佩服自己當初能把狀態理清楚調通,現在估計再也寫不出這種東西了,調模板編程就不是人應該乾的事。

https://github.com/jadedrip/lugce/blob/master/lugce/xml/simple_sax.hpp?

github.com

還有個東西是我當初比較得意的,用了一些模板元的技巧,搞出的 C++ 事件機制,還特地寫了篇博客:

CSDN?

mp.csdn.net

代碼在這裡:

https://github.com/jadedrip/lugce/blob/master/lugce/observer.hpp?

github.com


捂臉,沒錯,就是下面這行。

printf("古時的風箏 JDK
");

這是在學了 JVM 好長時間之後在 JDK 11 啟動方法上加的一行代碼。別小看這行代碼,此行代碼的作用是向全世界宣告這個 JDK 是屬於我自己一個人的版本。

完整代碼是加在這裡的:

int JNICALL
JavaMain(void * _args)
{
JavaMainArgs *args = (JavaMainArgs *)_args;
printf("古時的風箏 JDK
");
int argc = args-&>argc;
char **argv = args-&>argv;
int mode = args-&>mode;
char *what = args-&>what;
InvocationFunctions ifn = args-&>ifn;

JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
/* 省略 */
}

當使用這個 JDK 版本運行程序。

public static void main(String[] args) {
System.out.println("Hello JVM");
}

運行之後的效果是這樣的:

作為一個 Java 開發者,雖然自認 JVM 原理都掌握的比較熟練了,但是對 JVM 源碼還是敬而遠之的,主要是 C++ 完全不在射程範圍內。

雖然簡單的編譯一下源碼,改一行代碼,翻翻源碼並沒有什麼實際能力的提升,但是心理上會覺得,嗯,我又行了。

補充一下編譯 JDK 源碼的完整過程:

古時的風箏:做 Java 這麼久了,你編譯過 JDK 源碼嗎?

zhuanlan.zhihu.com圖標

既然C++也兼容C代碼,我就貼一個18年用C給我的操作系統寫的鍵盤驅動吧

#include &
#include &
#include &
#include &
#include &
#include &
#include &
#include &
#include &
#include &
#include &

#define CHECK_TTY(a)
if((a)==NULL){
(a)=tty_current();
}
current_tty=(a);

static struct tty *current_tty=NULL; // Caching the last-used TTY structure
char init=0;

static void kbd_irq(struct registers regs);

struct tty *tty_current(void)
{
return current_tty;
}

void tty_enable_cursor(uint8_t start,uint8_t end)
{
outb(VGA_CMD,0xA);
outb(VGA_DATA,(inb(VGA_DATA) 0xC0) | start);
outb(VGA_CMD,0xB);
outb(VGA_DATA,(inb(VGA_DATA) 0xE0) | end);
}

void tty_disable_cursor(void)
{
outb(VGA_DATA,0xA);
outb(VGA_DATA,0x20);
}

uint16_t tty_get_cursor(void)
{
uint16_t ret;
outb(VGA_CMD,0xE);
ret=inb(VGA_DATA) &&> 8) 0xFF);
ptr-&>cursor=offset;
}

size_t tty_write(struct tty *ptty,const char *data,size_t len)
{
size_t i;
for(i=0;i&

相关文章