今晚复习C语言的指针和数组。

指针

指针:指向一个变量的地址。

声明方式:

1
2
int *p;
char *q;

C语言ptr变量名的含义:pts就是**Pointer Recod(er)**的简写

指针的使用:定义指针变量、把变量地址赋值给指针、访问指针变量指向的地址的值。

空指针:指向0x0,啥也不是。

指针的值:指针的值的类型都是一样的,长度等于一个单位地址的长度的16进制数值(32位、64位等)

指针的类型:指针指向的数据类型必须是一个有效的 C 数据类型。

例如,在32位系统中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main (){
int *p;
char *t;
printf("Length of p: %d\n", sizeof(p));
printf("Length of t: %d\n", sizeof(t));
printf("Length of *p: %d\n", sizeof(*p));
printf("Length of *t: %d\n", sizeof(*t));
}

/*
执行结果:
Length of p: 4
Length of t: 4
Length of *p: 4
Length of *t: 1
*/

指针的算数运算

指针可以进行++、--、+、-等运算。

例:指针递增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

const int MAX = 3;

int main (){
int var[] = {10, 100, 200};
int i, *ptr;

/* 指针中的数组地址 */
ptr = var;
for ( i = 0; i < MAX; i++) {

printf("存储地址:var[%d] = %x\n", i, ptr );
printf("存储值:var[%d] = %d\n", i, *ptr );

/* 移动到下一个位置 */
ptr++;
}
return 0;
}

指针数组

可以通过指针来声明数组

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main (){
const char *names[4] = { "Tom", "Bob", "Tony", "Jack" };

int i = 0;
while ( i < 4 ) {
printf("Value of names[%d] = %s\n", i, names[i] );
i++;
}
return 0;
}

指向指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int main (){
int var;
int *ptr;
int **pptr;

var = 3000;

ptr = &var; // 取var的地址作为指针ptr的值

pptr = &ptr; // 取ptr的地址作为pptr的值

printf("&var(%x) => var(%d)\n", &var, var );
printf("&ptr(%x) => ptr(%x) => *ptr(%d)\n", &ptr, ptr,*ptr );
printf("&pptr(%x) => pptr(%x) => **pptr(%d)\n", &pptr, pptr, **pptr);

return 0;
}

/*
执行结果:
&var(ffe3b11c) => var(3000)
&ptr(ffe3b118) => ptr(ffe3b11c) => *ptr(3000)
&pptr(ffe3b114) => pptr(ffe3b118) => **pptr(3000)
*/

指针作为函数参数

当指针(地址)作为函数参数时,函数可以在执行过程中改变指针所指向值:
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <time.h>

void getSeconds(unsigned long *par);

int main (){
unsigned long sec;
printf("[main] &sec(%x)=>sec(%ld): \n", &sec,sec);
getSeconds( &sec );
printf("[main] sec: %ld\n", sec );
return 0;
}

void getSeconds(unsigned long *par){
printf("[getSeconds] &par(%x)=>par(%x): \n", &par, par);
/* 获取当前的秒数 */
*par = time( NULL );
return;
}

指针作为函数返回值

指针作为函数返回值时,调用方可以直接访问这个地址:
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

int * getList();

int main (){
int * i;
int j;
printf("[main] &i(%x) => i(%x)\n", &i,i);
i = getList();
printf("[main] &i(%x) => i(%x)\n", &i,i);
//for(j=0;j<5;j++)
// printf("%d\t", i[j]);
return 0;
}

int * getList(){
static int list[5]={1,2,3,4,5};
printf("[getList] &list(%x) => list(%x)\n", &list,list);
return list;
}

/*
执行结果:
[main] &i(ff94f03c) => i(5661b25b)
[getList] &list(5661e01c) => list(5661e01c)
[main] &i(ff94f03c) => i(5661e01c)
*/

数组

一维数组

1
2
int arr[] = {1,2,3,4,5};
char list[] = {"Tom", "Tony"};

多维数组

多维数组可以通过在括号内为每行指定值来进行初始化:

1
2
3
4
5
6
7
8
9
10
int a[3][4] = {  
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};

// Or

int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

多维数组的访问:

1
int val = a[2][3]; //获取数组中第2行第3个元素

数组作为函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

/* 函数声明 */
double getAverage(int arr[], int size);

int main (){
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;

/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;

/* 输出返回值 */
printf( "平均值是: %f ", avg );

return 0;
}

double getAverage(int arr[], int size){
int i;
double avg;
double sum=0;

for (i = 0; i < size; ++i) {
sum += arr[i];
}

avg = sum / size;

return avg;
}

数组作为函数返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* 要生成和返回随机数的函数 */
int * getRandom(){
static int r[10];
int i;

/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i){
r[i] = rand();
printf( "r[%d] = %d\n", i, r[i]);
}

return r;
}

/* 要调用上面定义函数的主函数 */
int main (){
/* 一个指向整数的指针 */
int *p;
int i;

p = getRandom();
for ( i = 0; i < 10; i++ ) {
printf( "*(p + %d) : %d\n", i, *(p + i));
}

return 0;
}

指针和数组的关系

在C语言中,数组名是一个指向数组中第一个元素的常量指针,例如

1
double balance[50];

这个数组中,balance是一个指向&balance[0] 的指针,即数组 balance 的第一个元素的地址。

因此,把 p 赋值为 balance 的第一个元素的地址:

1
2
3
4
double *p;
double balance[10];

p = balance;

使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。

把第一个元素的地址存储在 p 中,就可以使用 *p、*(p+1)、*(p+2) 等来访问数组元素(也等同于*list+1)。

今晚还是在研究相对轻松的内容。主要涉及C语言的函数、作用域、预处理和头文件。

函数的声明

C语言中函数的定义:

1
2
3
4
return_type function_name( parameter list )
{
body of the function
}

C语言中函数的传值调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

void swap(int x, int y)
{
int temp;

temp = x;
x = y;
y = temp;

return;
}
int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;

printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );

swap(a, b);

printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );

return 0;
}

C语言函数的引用调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */

return;
}

int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;

printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );

/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);

printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );

return 0;
}

C语言可变参数的函数:

在C语言标准库中,stdarg.h头文件用于支持可变参数的函数,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:
定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
使用宏 va_end 来清理赋予 va_list 变量的内存。

具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdarg.h>

//一个求平均数的函数
double average(int num,...){
va_list valist;
double sum = 0.0;
int i;

/* 为 num 个参数初始化 valist */
va_start(valist, num);

/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++){
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);

return sum/num;
}

int main(){
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

函数的作用域

  1. 在函数或块内部声明的是的局部变量
    局部变量只能在函数内部使用。

  2. 在所有函数外部声明的是全局变量
    全局变量即可在函数内部使用,也可在函数外部使用。全局变量通常声明在程序的顶部,其在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。

局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用。

全局变量与局部变量在内存中的区别:

全局变量保存在内存的全局存储区中,占用静态的存储单元;
局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>  
#include <stdlib.h>
int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main(){
static int m1 = 2, m2;
int i = 1;
char*p;
char str[10] = "hello";
char*q = "hello";
p = (char *)malloc(100);
free(p);
printf("栈区-变量地址 i:%p\n", &i);
printf("栈区-变量地址 p:%p\n", &p);
printf("栈区-变量地址 str:%p\n", str);
printf("栈区-变量地址 q:%p\n", &q);
printf("堆区地址-动态申请:%p\n", p);
printf("全局外部有初值 k1:%p\n", &k1);
printf(" 外部无初值 k2:%p\n", &k2);
printf("静态外部有初值 k3:%p\n", &k3);
printf(" 外静无初值 k4:%p\n", &k4);
printf(" 内静态有初值 m1:%p\n", &m1);
printf(" 内静态无初值 m2:%p\n", &m2);
printf(" 文字常量地址:%p, %s\n", q, q);
printf(" 程序区地址:%p\n", &main);
return 0;
}

/*
执行结果:
栈区-变量地址 i:0xff9f9e8c
栈区-变量地址 p:0xff9f9e88
栈区-变量地址 str:0xff9f9e7e
栈区-变量地址 q:0xff9f9e78
堆区地址-动态申请:0x574ef160
全局外部有初值 k1:0x56644024
外部无初值 k2:0x5664403c
静态外部有初值 k3:0x56644028
外静无初值 k4:0x56644034
内静态有初值 m1:0x5664402c
内静态无初值 m2:0x56644038
文字常量地址:0x56642008, hello
程序区地址:0x566411b9
*/

  1. C语言的形参与实参

形参(形式参数)
在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
实参(实际参数)
函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。

形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。

形参和实参的区别和联系

  1. 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。

  2. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。

  3. 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。

  4. 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。

预处理和头文件

C语言的预处理器

C 预处理器相当于一个文本替换工具,会指示编译器在实际编译之前完成所需的预处理。C 预处理器(C Preprocessor)简写为 CPP。
所有的C预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
下面列出了所有重要的预处理器指令:

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

一些样例:

1
2
3
//这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
#undef FILE_SIZE
#define FILE_SIZE 35
1
2
3
4
//这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
1
2
3
4
5
6
7
#ifdef DEBUG
/* Your debugging statements here */
#endif
/*
这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。
它定义了 DEBUG,可在编译期间随时开启或关闭调试。
*/

预定义宏

ANSI C 定义了许多宏。在编程中可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
__DATE__ 当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。
__TIME__ 当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。
__FILE__ 这会包含当前文件名,一个字符串常量。
__LINE__ 这会包含当前行号,一个十进制常量。
__STDC__ 当编译器以 ANSI 标准编译时,则定义为 1。

C语言头文件
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。

只引用一次头文件

1
2
3
4
5
6
#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

有条件引用

有时需要从多个不同的头文件中选择一个引用到程序中。

例如,需要指定在不同的操作系统上使用的配置参数:

1
2
3
4
5
6
7
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif

吃完饭,洗过碗,开始第一天的活动。

今天的复习内容最轻松,因为是基础中的基础。主要涉及C语言的编译器、语法关键字、运算符、变量及数据类型。

编译器

C语言在Linux上的编译器:GCC

编译tips:使用GCC在64位电脑上编译32位程序

1
2
3
4
5
6
gcc -m32 -o test test.c

#先要安装build-essential module-assistant gcc-multilib g++-multilib

apt-get install build-essential module-assistant gcc-multilib g++-multilib

C语言在Windows上的编译器:CL.EXE
Windows上的集成开发环境,通常是Visual Studio,目前最新的已经是VS 2019版本了,与早些年不同的是,VS Community版本目前对个人是免费的。
但是因为一直以来的习惯,我还是习惯用VC6.0作为我的Windows C语言开发环境。编译连接仍然采用CL.EXE和LINK.EXE,以及NMAKE.EXE。

基本的编译语法

以Hello World为例,代码如下:

1
2
3
4
5
6

int main(){
printf("Hello World\n");
return 0;
}

Linux(Debian)下的编译过程
gcc -o helloworld helloworld.c

Windows下使用CL.EXE进行编译

1
2
cl.exe /c helloworld.c
link helloworld.obj /LIBPATH:C:\Tools\Binary\VC++6.0\VC98\Lib\

这里C:\Tools\Binary\VC++6.0\VC98\Lib\是我的VC6路径,并且执行cl和link需要将程序路径加入到环境变量。

语法关键字

1
2
3
4
5
6
7
auto  break case  char  const  
continue default do double else
enum extern float for goto
if int long register return
short signed sizeof static struct
switch typedef unsigned union void
volatile while

部分关键字含义

  • continue 结束当前循环,开始下一轮循环
  • extern 声明变量或函数是在其它文件或本文件的其他位置定义
  • goto 无条件跳转语句
  • register 声明寄存器变量
  • typedef 用以给数据类型取别名
  • volatile 说明变量在程序执行中可被隐含地改变

运算符

基本运算符

加减乘除、取模、自增、自减: +,-,*,/,%,++,–

赋值运算符

1
2
3
4
5
6
7
8
9
10
11
12
=:简单的赋值运算符,把右边操作数的值赋给左边操作数    C = A + B 将把 A + B 的值赋给 C
+=:加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-=:减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*=:乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
/=:除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%=:求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<=:左移且赋值运算符 C <<= 2 等同于 C = C << 2
>>=:右移且赋值运算符 C >>= 2 等同于 C = C >> 2
&=:按位与且赋值运算符 C &= 2 等同于 C = C & 2
^=:按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
|=:按位或且赋值运算符 C |= 2 等同于 C = C | 2

逻辑运算符

1
与或非:&&,||,!

位运算符

1
与、或、异或、取反、左移、右移:&,|,^,~,<<,>>

杂项运算符

1
2
3
&    返回变量的地址
* 指向一个变量
? : 条件表达式三元运算符

变量及数据类型

基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
整数类型:
char 1 字节 -128 到 127 或 0 到 255
unsigned char 1 字节 0 到 255
signed char 1 字节 -128 到 127
int 2 或 4 字节 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2 或 4 字节 0 到 65,535 或 0 到 4,294,967,295
short 2 字节 -32,768 到 32,767
unsigned short 2 字节 0 到 65,535
long 4 字节 -2,147,483,648 到 2,147,483,647
unsigned long 4 字节 0 到 4,294,967,295

浮点类型:
float 4 字节 1.2E-38 到 3.4E+38 6 位小数
double 8 字节 2.3E-308 到 1.7E+308 15 位小数
long double 16 字节 3.4E-4932 到 1.1E+4932 19 位小数

枚举类型

可以在定义枚举类型时改变枚举元素的值:

enum season {spring, summer=3, autumn, winter};

没有指定值的枚举元素,其值为前一元素加1。此例中spring的值为0,summer的值为 3,autumn 的值为4,winter 的值为5。

三种写法

  1. 先定义枚举类型,再定义枚举变量
1
2
3
4
5
6
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

  1. 定义枚举类型的同时定义枚举变量
1
2
3
4
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
  1. 省略枚举名称,直接定义枚举变量
1
2
3
4
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

引用枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

enum DAY {
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

int main() {
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}

应该找点让自己开心的事情做。

C语言是我进入计算机领域学习的第一门语言。可惜工作这么多年,早已忘的支离破碎,对链表、数据结构乃至系统编程的知识,早已经还给老师了。这对今后的学习是非常不利的。所以计划从今天开始,抽空花30个晚上,每晚花1~2个小时的时间复习C语言,尽量保证连续。

我的复习会比较跳跃,这个系列的文章,也仅适合作为个人的技术笔记,不太适合作为入门资料进行参考。

我可能会在文章中提及一些调试、算法以及工程方面的技术,相信其中有些内容对搜索引擎来说会是一个比较好的长尾关键词,这样也给一些编程过程中遇到问题的朋友一些参考。本着少废话的原则,尽量避免探讨过于简单的技术。否则可能会沦为小白记事本,反而容易误导网友。

暂时给自己拟定了以下复习计划,30个夜晚主要这样安排(后续可能根据实际情况有所调整):

第一夜:编译器、语法关键字、运算符、变量及数据类型
第二夜:函数、作用域、预处理和头文件
第三夜:指针和数组
第四夜:存储类型、结构体、链表
第五夜:C语言标准库、错误处理和调试、输入输出
第六夜:内存管理、读写文件、管道
第七夜:线程、多线程
第八夜:进程、多进程
第九夜:C语言常见的Linux系统调用
第十夜:C语言常见的Windows系统调用
第十一夜:在C语言中使用内联汇编
第十二夜:使用C语言探索堆栈
第十三夜:网络编程1-网络协议及其数据结构
第十四夜:网络编程2-Socket通信
第十五夜:网络编程3-常见的应用层协议
第十六夜:编写一个Linux的服务
第十七夜:编写一个Windows的服务
第十八夜:C语言动态链接与静态链接
第十九夜:编写一个Linux用户态Trojan
第二十夜:编写一个Windows用户态Trojan
第二十一夜:Linux内核编程1-内核源代码分析
第二十二夜:Linux内核编程2-实现一个内核模块
第二十三夜:Linux内核编程3-实现一个块设备驱动
第二十四夜:Linux内核编程4-实现一个字符设备驱动
第二十五夜:Linux内核编程5-实现一个内核Trojan
第二十六夜:Linux内核编程5-在Linux内核中瞎搞
第二十七夜:Windows内核编程1-Windows内核初探
第二十八夜:Windows内核编程2-Windows驱动编程研究
第二十九夜:Windows内核编程3-在Windows内核中瞎搞
第三十夜:学习总结

真是一个宏大的计划,甚至不知道自己能不能完成,有点期待。

希望可以自律、坚持地完成下去。

参考资料:

Linux kernel下的内核socket编程
Linux系统编程

在看过新海诚的《君の名は。》之后,感动的同时,非常喜悦。
我曾在千与千寻这篇文章中,写过宫崎骏成长的环境,简单表明了,时代造就大师这一观点。

新海诚,就是让人感到他是日本漫画在宫崎骏之后又一位大师,为这个世界带来满满诚意。

我说过我们老了以后不会无聊,事实上在我们还如此年轻的时候,就已经不无聊了。

在豆瓣看了这样一篇关于天气之子的影评,自觉表达功力粗浅,看到这样字字珠玑的影评,满脑子只有刘关张结拜时张飞的那句”俺也一样“,带着一种热泪盈眶。

文艺评论并不是我的特长,每次写所谓的影评,我常常容易看起来跑题,不怎么提及影片本身,而更多的出离作品,结合我自己对生活和艺术的主观感受,去品味作品的灵魂。

日本比我们更早进入繁荣时期,曾经的日本年轻人比我们更早地遭遇到经济发展、社会进步所伴随的迷茫与困惑。他们的如今,折射出我们的今后。

我一直将日本这个国家视作一个有趣的观察对象。他们和我们拥有同源的东亚文化,可以追溯到盛唐时期,甚至在某些方面,比我们保存的更完好,例如建筑、礼仪、料理等等。在亚洲的民族当中,日本一直是一个学习能力很强的民族。在古代,他们向中国学习先进的文化,并良好地继承和发扬。在近代,他们从欧美学习机械和电子,并在20世纪末创造了空前的经济辉煌。这无疑是亚洲班里一位踏实的尖子生。

为什么观察日本?可以认为日本就是东亚文明升级过程的一个小型实验场。在这个实验场里可以看到东西方文化的交融、碰撞,传统文明与现代文明的对立、统一,而这些现象,放在我们的社会同样适用。社会在发展过程中,很多事物当然应该以史为鉴。而如果有一个能够把玩和观测的经验模型,岂不更妙?

我们这代人此时所遭遇的困惑和际遇,也正是日本上一个阶段的年轻人所经历过的。他们也经历过科技的进步、房市的繁荣、股市的爆发,乃至美国的打压。蜡笔小新五岁的时候,家里房贷还剩下30年,美芽喜欢逛商场,广志带回家一台笔记本电脑,一家人偶尔旅行和野餐。这些小民生活的方方面面,如今只不过是换了时间和形态,重演在我们的人生历程中。他们的社会就像是时空隧道传出的一道剪影,让我们提前预知一些事物的发展方向。事实上,由于世界局势发展的不确定性,能透过他们预知的距离也越来越短。我们正在一边观测,一边超越。今天东京街头的年轻人,可能和首尔、洛杉矶、上海的年轻人的思想差不了太多。

漫画有时是对生活的解构和期望。很多在现实中难以如愿的,就去漫画里圆满、去发泄。

看新海诚的《天气之子》,最多的感触是,社会角落中生活的人们,他们眼中的天空,是怎样的耐人寻味。

他们的快乐和痛苦,他们的青春和爱情。他们那些刻骨铭心的记忆,他们那些难以割舍的爱人。

社会的条条框框,约束人生的方方面面,将棱角分明的少年,磨成世故圆滑的大人。

习惯了资本的游戏,舍弃了纯粹和天真。既抱怨着透不过气的生活,却又践踏着自由的土壤。

厌恶荒诞戏谑的现实,又更卖力地表演着丛林法则。没有放弃一切的勇气,却还觊觎灿烂美好的晴天。

这解释了为什么,年轻的晴女拥有了治愈东京的神力,也愿意抛弃,只选择和所爱的人在一起,守望朝夕。

新开辟了一个博文分类,社会
分类叫做社会,但是却不怎么敢聊社会。至少有些话题我是不敢聊的。
但是今天,还是来随便聊两句,作为这个系列的开篇(不保证有后续)。

从某厂“不让”注销说起

如果你是一位这厂用户并且你能幸运地找到以上页面,你也许会有知乎上这个问题一样的困扰。

为什么大部分的中国互联网公司,都把注销功能做的不尽如人意甚至压根不做呢?这个问题实在太深刻,深刻到令我无从解读。

其实根据工信部《电信和互联网用户个人信息保护规定》第九条,用户完全有注销帐号的权利。但很多互联网公司都没有为用户提供这项权利。

第九条 未经用户同意,电信业务经营者、互联网信息服务提供者不得收集、使用用户个人信息。

  电信业务经营者、互联网信息服务提供者收集、使用用户个人信息的,应当明确告知用户收集、使用信息的目的、方式和范围,查询、更正信息的渠道以及拒绝提供信息的后果等事项。

  电信业务经营者、互联网信息服务提供者不得收集其提供服务所必需以外的用户个人信息或者将信息用于提供服务之外的目的,不得以欺骗、误导或者强迫等方式或者违反法律、行政法规以及双方的约定收集、使用信息。

  电信业务经营者、互联网信息服务提供者在用户终止使用电信服务或者互联网信息服务后,应当停止对用户个人信息的收集和使用,并为用户提供注销号码或者账号的服务。

  法律、行政法规对本条第一款至第四款规定的情形另有规定的,从其规定。

也有一些公司为用户提供了“注销”这项功能,但注销删除是有区别的,我相信用户选择注销帐号,更多的是希望能够从数据库中将自己的信息删除。然而从程序员的角度来看,对数据库执行一个删除操作,显然要比增加一个删除标记字段的风险大的多。尤其是对于现在靠用户数据卖钱的公司来说,删除用户,那就是在割自己田里的韭菜苗,挖韭菜根啊。何况,工信部的规定,也并没有提到要删除,只说需要提供注销的功能。你要注销,可以,把你的账号锁定,手机号释放,禁止你登录,标记为注销。

更何况,现如今很多公司都提倡什么微服务、数据中台,本来一个MySQL就能搞定的数据,非要在Redis里放一点,Mongo里面放一点,ES里面放一点,这就导致如果要真的从系统中完整删除一个帐号,远比想象的要复杂,数据和业务之间可能会有错综复杂的裙带关系。你要问这些公司为啥要这样做?很简单,员工要吃饭,上司要成绩。不上新架构新业务,经费怎么花?奖金谁来拿?从老板层面而言,就更是如此。养着一帮程序员,工资又那么高,不让他们做点事情压榨压榨,自己岂不是亏了。可是业务是固定的,用户也是稳定的,让他们做啥好呢?那就什么新做什么吧,这阵子大数据新,就上大数据,过阵子AI火,那就用AI,区块链概念好,那就玩区块链……

互联网圈里很多人有个臭毛病,就是代码也许写不了几行,但是吹起NB绝对是最靓的仔。

互联网公司个个看起来高大上,可是大部分不过是CRUD功能堆砌起来的信息垃圾堆而已。如果不清楚什么是CRUD,就这么跟你说吧,CRUD说白了就是基础的增删查改(Create,Retrieve,Update,Delete)。

以SNS网站为例,其基础功能从逻辑本质上来看,无非就是信息创建、信息修改、信息索引,就是增删查改。并没有什么高大上,反倒是业务庞大了以后,雇了太多活儿糙的程序员,导致部门臃肿,架构复杂,业务更加垃圾。

说到这里,有些代码孔乙己可能会出来杠。算法和架构的事,能叫CRUD吗?紧接着,便是分布式缓存、后端渲染、大数据、机器学习……写字间里又传来一阵快活的笑声。

我根本不想杠,写博客本来就图个乐。

不过话说回来,也不能小瞧这些创造垃圾的公司,他们割起韭菜可是很有水平的。不信你看,中文互联网公司,盈利的高招儿,我来悉数一下:

做文档的公司,放贷款;
做手机的公司,放贷款;
做社交的公司,放贷款;
做视频的公司,放贷款;
做搜索的公司,做医疗;
以上公司,也偶尔插播植发广告。

美名其曰运营。实际上是杀猪。在他们眼里,用户就是猪,也许有的时候,用户还不如猪。

聊聊互联网自由

前面说到互联网公司的问题,不得不进一步思考。互联网发展至今,是否还有人记得它的初心?互联网究竟是解放了人类,还是为人类制造了新的枷锁?

互联网最开始,人们希望的是拥有自由、开放、共享的互联网,让人们可以在浩瀚的数字海洋里畅快地傲游。可如今,TikTok被威胁,让人甚至感觉到一种魔幻。曾经我们以为大洋彼岸的那块土壤,可以繁衍出自由的互联网。可如今再看,所谓自由不过也是游戏规则制定者嘴里的说辞罢了。

放眼整个互联网世界,如今似乎正在经历一场浩劫。欧洲、俄罗斯,都在考虑封锁自己国家的互联网。真不知道今后,还有没有能够让全人类互相连接甚至连接一切的互联网。想起圣经里面提到的,上帝为了阻止人类建造通往天堂的巴别塔,把人类分散在世界各地,让他们说不同的语言,从而无法协作。而曾经人们一度相信互联网可以让全人类产生紧密的联系,可以促进一切的发展。然而事实上,互联网却只沦为了少部分人的舆论场,投机分子的势利场,资本家们的养猪厂,犯罪的温床。

虽然也有过奇妙的一幕,就如维基解密棱镜门、巴拉拉文件、各种门。微博也曾经为寻人、破案、反腐带来了一些不可思议的效果。

可互联网世界更多的却还是充斥着一种难以名状的混沌。

这一切因果,究竟是互联网的罪恶基因在作怪,还是人类的秉性使然?

是否自由就意味着混乱?是否以人类的本性,根本就不配拥有互联网这样的观照之境来普渡众生。

什么时候互联网的世界,才可以有一片纯净的天空。人类可以利用互联网,传播真正有价值的信息和言论,透明而严谨的真相,而不是140个字的废话,声色犬马。

也许需要先提高全体人类自身的认知水平,才能逐渐实现这一切。也许,没等到提高全体人类的认知水平,这一切就已经毁灭在黎明前夕。

总之,今天我已经选择注销大部分社交帐号,选择在浩瀚的数字海洋里,自闭成为一座孤岛,就像这个网站一样。

选择阅读更多纸质书籍,投入于更多经典,沉下心来,去踏实研究这世间的学问。

我相信,即使是一艘小船,也可以纵横四海,阅尽千帆。

海船

2020/07/26摄于 南京-大胜关

很久没有写字了,但却从未停止思考。这么一方小天地,自是无人问津,可以畅快表达。

永恒的事物,必要时间去证明它的价值 – 或是它的荒诞。

思考太多却很少见诸笔端的主要原因有三:

一是不够自信,一直处于自我怀疑之中;
二是读书太少,更加不敢开口,怕暴露智商;
三是不敢胡说,文字表达天生存在缺陷,开口即是错。

在我自己开辟的一方赛博领土之上,我倒是愿意说一些莫名其妙的真话。但最好也是猥琐到让人不能轻易看懂,自己也安全。

今天就来谈谈,很久以来,一些一直在思考却从未有结果的事情。我想很多我早已想通的道理,而世界却未必明白。

所以我将它写下来,孤独的思想者,把一切苍白的辩驳都交给时间。

眼前的光景,是荒唐的人间。世界观没来得及稳固就被颠覆的感觉,不知道是否有人能够体会。

我们的生活,B站、抖音,歌舞升平,一浪高过一浪。生活仿佛从未发生变化甚至快要高潮一样,而这正是当下最该警惕的地方。

不禁想回忆起,从什么时候开始觉得生活变得逐渐不一样?我觉得是从2012年。

混沌中心

混沌是什么?混沌是无法描述的,就如此刻我想诉说的事物,它无法被琢磨,无法被找寻,无法被把握。

但能清楚的感受到它的存在。更进一步的,是能感知到,它将去向哪儿。

我们身处混沌的中心,眼前充斥着一切迷雾,混沌假以掩饰,令我们亦步亦趋。

我们的生活,如今被互联网所支配。而互联网是一个无法停止的事物,一旦启动,就必将改变眼前的一切。

而有一些价值观,需要被表达,也是此文的意义。

可能有些人,看到这里,就不再继续读下去了。这也正是每当想要对这个世界阐述一种立场时,最悲观的地方。

黑客精神

黑客在看待一个问题时,首先是质疑。如果想要知道一道上锁的门是否可靠,只需要推敲即可。记住,质疑,再然后是推敲。

世上之事,大抵如此。从SQL注入,到人生的意义。其实世上之事,大都经不起推敲。

地球上所有的信息系统,都有漏洞。甚至可能连数学理论本身都有漏洞。如果你认为printf(“hello world\n”)这样的代码不会有漏洞,请看看射线导致磁盘、内存位反转的案例。类似这样的,甚至更加古怪的问题,人类黑客,可能永远也无法发现它。而很多时候逻辑深处的漏洞,甚至比物理漏洞还要隐蔽。之所以常人觉察不到,仅仅是因为认知和格局还无法辅佐我们抵达。

可悲的是,如今大部分具有黑客思维的人,所从事的工作,竟然是在给这个世界上最愚蠢的程序猿们开发的CRUD系统寻找所谓的安全问题!更可悲的是,很多在质疑和推敲方面具有良好潜质、良好的思辨能力的选手,竟沉迷在这样的工作当中无法自拔,自我满足。

黑客生存的意义,绝不应该局限在给信息系统寻找漏洞这件经不起推敲的事情。当好不容易具备了与别人不一样的怀疑视角,一定不要把这种能力埋没在和傻瓜比数数,以及指导他们做作业这些无聊事情上。而是应该去研究金融、经济、Politics、管理。当然,如果你的脑核足够富余,哲学和数学更是个不错的选择。

只提出愿景,而忽视现实,是非常惹人讨厌的。你可能会说,黑客也是人,黑客要吃饭,养家糊口,就必须去做一些很傻的事情。其实这是经不起推敲的,看透一切事物的本质,便知道物欲也是虚无。穷且益坚,求真务实,就成为了一个黑客该有的​追求。

其实,只要能理解,这个世界上,大部分事情,都经不起推敲的​道理,就不会再去追求物质。

​​就说金钱吧。在以黑客思维去看待地球文明时,货币系统只各个互斥的群体为了内部耦合与外部协作而构建出的一套​算法罢了。货币、贵金属、古董,乃至一切等价物,都只是对​价值结构体的引用罢了。

个体的贫穷和富有,取决于这套算法的​实现逻辑。掌握了这套算法,未必能实现富裕。有commit的权限和编码的水平,兴许倒可以实现利益最大化。

但是跳脱出这个系统,才是黑客精神。仅仅沉迷在这套系统之间,还是​没有意义。

最近的这几个VirtualBox版本(6.1.4、6.1.6、6.1.8)比以前多了一些变化,主要体现在Guest Additions上。

在6.1.4、6.1.6的版本里,曾经遇到过虚拟机和宿主机(Guest and Host)之间不能共享粘贴板(Clipboard)的bug,以及Windows 7、Debian10的Guest机器无法全屏的Bug。

之前的解决方法是,在 VirtualBox官网上下载Virtualbox 6.1.2版本的Guest Additions ISO,挂载并且安装ISO镜像中的VBoxLinuxAdditions.run文件后,即可解决这些问题。

但在今天的6.1.8版本的Release Changelog中,提到已经解决了多个这样的Bug,于是试试运气,结果遇到这样的问题:

1
VboxClient: The parent session seems to be non-X11. Exiting...

这里分享我在Windows 10 Host,Debian 10.0 Guest上的解决方法:

先注销当前用户:

再在登录的时候选择X11选项:

即可解决。

0x00 前言

Rails 6.0 发布已经有一段时间了,之前没有什么项目可以练手,最近总算遇到可以使用Rails 6开发的场景了。Rails 6中新增了一些功能和特性,有一些内容官方文档里并没有写的很具体,所以在摸索的过程中也遇到了一些坑点。这里以一个新工程从头到尾的构建,来简单记录在Rails6中使用jQuery和Bootstrap的过程。希望能够给新来的同学做个参考。

编程和码字的水平有限,如有错漏敬请指教,也请多包涵!下面就开始吧。

0x01 环境准备

  • 操作系统: Debian 10 ( 4.19.0-8-amd64 )
  • Ruby版本: ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]
  • Rails版本: Rails 6.0.2.1
1
2
3
4
5
6
7
test@debian:~$uname -a
Linux debian 4.19.0-8-amd64 #1 SMP Debian 4.19.98-1 (2020-01-26) x86_64 GNU/Linux
test@debian:~$ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]
test@debian:~$rails -v
Rails 6.0.2.1

0x02 创建项目

这里新建一个项目“TestApp”,并在其中建立控制器“Test”和测试方法“test”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
test@debian:~$rails new TestApp
create
create README.md
create Rakefile
create .ruby-version
create config.ru
create .gitignore
create Gemfile
...
├─ [email protected]
├─ [email protected]
└─ [email protected]
Done in 59.74s.
Webpacker successfully installed 🎉 🍰
test@debian:~/TestApp$rails g controller Test test --no-stylesheets
Running via Spring preloader in process 22413
create app/controllers/test_controller.rb
route get 'test/test'
invoke erb
create app/views/test
create app/views/test/test.html.erb
invoke test_unit
create test/controllers/test_controller_test.rb
invoke helper
create app/helpers/test_helper.rb
invoke test_unit
invoke assets
invoke scss
test@debian:~$

创建好之后,修改test页面,添加一个表单和按钮,用于稍后测试Bootstrap。(在执行创建控制器和方法的命令后,Rails已经为我们自动添加了到Test控制器test方法的路由,所以不需要我们再新增路由。)

app/views/test/test.html.erb 代码:

1
2
3
4
5
6
7
<h1>Test#test</h1>
<p>Find me in app/views/test/test.html.erb</p>

<form>
<input id="test" type="text"/>
<button id="test_btn">点我</button>
</form>

启动Rails服务,访问http://localhost:3000/test/test:

1
2
3
4
5
6
7
8
9
10
11
test@debian:~/TestApp$rails s
=> Booting Puma
=> Rails 6.0.2.1 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.6.5-p114), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop

此时应用已经创建成功。

0x03 添加jQuery和Bootstrap库

这里采用yarn作为Javascript包管理器。在TestApp目录中运行bin/yarn,如果出现以下内容,说明yarn没有安装:

1
2
3
test@debian:~/TestApp$bin/yarn
Yarn executable was not detected in the system.
Download Yarn at https://yarnpkg.com/en/docs/install

安装方法在这里, 以当前使用的Debian 10为例,安装的方法是:

1
2
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

安装好yarn之后,就可以用yarn添加jQuery包和Bootstrap包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
test@debian:~/TestApp$yarn add jquery 
yarn add v1.21.1
[1/4] Resolving packages...
[2/4] Fetching packages...
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > [email protected]" has unmet peer dependency "webpack@^4.0.0".
[4/4] Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
Done in 9.98s.
test@debian:~/TestApp$yarn add bootstrap
yarn add v1.21.1
[1/4] Resolving packages...
[2/4] Fetching packages...
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > [email protected]" has unmet peer dependency "webpack@^4.0.0".
warning " > [email protected]" has unmet peer dependency "popper.js@^1.16.0".
[4/4] Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
Done in 13.33s.

由于Bootstrap还需要基于popper.js,不装的话会报依赖错误,所以安装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
test@debian:~/TestApp$yarn add popper.js
yarn add v1.21.1
[1/4] Resolving packages...
warning [email protected]: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1
[2/4] Fetching packages...
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > [email protected]" has unmet peer dependency "webpack@^4.0.0".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
Done in 11.78s.

安装的执行结果:

因为众所周知的原因,网速慢的同学就尴尬了,这里可能要等很久,所以你需要选一个科学一点的方式。

此时查看TestApp/node_modules目录,所需的前端库已经添加好了:

1
2
3
4
5
6
7
test@debian:~/TestApp$ls node_modules/jquery/
AUTHORS.txt bower.json dist external LICENSE.txt package.json README.md src
test@debian:~/TestApp$ls node_modules/bootstrap/
dist js LICENSE package.json README.md scss
test@debian:~/TestApp$ls node_modules/popper.js/
dist index.d.ts index.js.flow package.json README.md src
test@debian:~/TestApp$

0x04 使用Bootstrap

跟Rails5有所不同的是,Rails6采用webpack打包的方式,把需要打包的资源统一放在一个文件里(Rails6之前是采用Gemfile的方式引入第三方库,再通过Asset Pipeline汇聚)。

于是在安装好所需的前端库之后,就需要在app/javascript/packs/application.js中先引用它:

以Bootstrap为例,在其中添加import 'bootstrap'引入语句:

与此同时还需要在application.scss中引入Bootstrap的CSS样式表:

app/assets/stylesheets/application.scss文件中添加@import "bootstrap/dist/css/bootstrap";


2020/05/28 Patch

纠个错,如果想要通过@import "bootstrap/dist/css/bootstrap";的方式引用bootstrap相关的CSS,需要把app/assets/stylesheets/application.css文件重命名为app/assets/stylesheets/application.scss
如果直接使用app/assets/stylesheets/application.css的话,那么采用如下方式来引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* File: app/assets/stylesheets/application.css
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require bootstrap/dist/css/bootstrap
*= require_tree .
*= require_self
*/

Ending Patch


接着继续修改app/views/test/test.html.erb文件中的代码,添加相应的CSS类来测试Bootstrap是否已经引入成功:

1
2
3
4
5
6
7
8
9
<h1>Test#test</h1>
<p>Find me in app/views/test/test.html.erb</p>

<form>
<div class="form-group">
<input id="test" type="text" class="form-control"/>
<button id="test_btn" class="btn btn-success">点我</button>
</div>
</form>

启动应用,再次访问测试页面:

虽然界面有点丑,但是从渲染效果来看,Bootstrap已经成功引入了。

0x05 使用jQuery

继续修改app/javascript/packs/application.js,添加用于测试jQuery的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")


// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

import 'bootstrap'

$(document).on('turbolinks:load', function(){
$("#test_btn").click(function(){
alert($("#test").val());
});
});

保存并运行,刷新https://localhost:3000/test/test页面,发现添加的代码无法执行,控制台中有报错:

1
2
ReferenceError: $ is not defined 
application.js:21

这是因为jQuery库也需要引入一下,但是引入的方式有些不同,并不是在页面中直接引入,而是需要在config/webpack/environment.js中添加以下引入代码:

1
2
3
4
5
6
var webpack = require('webpack');
environment.plugins.append( 'Provide',
new webpack.ProvidePlugin({
$: 'jquery',
})
)

通过声明一个全局的导出,就可以在全局的JS代码文件中使用”$”符号了。

刷新并再次访问,代码能够运行,控制台也没有再报错,说明jQuery已经成功集成:

另一种方法

还有一种方法是通过在JS文件按中使用import $ from 'jquery'来使”$”符生效,但是这样比较繁琐,需要在每个JS文件中出现,所以不再介绍了,当然,也提供一下详细的代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")


// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

import 'bootstrap'
import $ from 'jquery' // 在每个JS文件中加上这一行,也可以替代声明全局变量。

$(document).on('turbolinks:load', function(){
$("#test_btn").click(function(){
alert($("#test").val());
});
});

0x06 总结

在网上主要参考了这篇文章。Rails 6中的变化还是挺大的,不知道webpack会不会真的成为Rails的未来,感觉之前使用coffee script来写JS的同学不是很多,但是我倒是已经非常习惯于使用它来开发前端功能了。

但是想必Rails的先驱们有着他们更成熟的考虑吧,那就一起继续探索好了。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×