Misaki`s blog

学习是一种态度


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

【C语言】第十章-字符串及内存函数

发表于 2019-01-31 | 分类于 C语言进阶
字数统计: 3k

第十章 字符串及内存操作函数

  在这一章我们重点讲解几个字符串及内存的标准库函数,这些函数都是标准库中提前准备好了的,但是我们有必要了解它们的功能,并且模拟它们的实现,这对我们将来的程序编写有很多指导性意义。

字符串函数

strlen()

  这个函数我相信大家再熟悉不过了,就是普通的计算字符串长度的函数,不过我们要注意这个函数传入的参数必须是一个以空字符结尾的字符串。不然就会出现未定义行为。这个函数返回的长度是字符串不包含末尾空字符的长度。这点很重要,很多同学都会在这点上出错。接下来我们对这个函数进行基本实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
size_t Strlen(const char* str)
{
assert(str != NULL);//有效性检验
size_t i = 0;
while(str[i] != '\0')
{
++i;
}
return i;
}
int main()
{
char str[] = "123";
printf("%d\n", Strlen(str));
}



[misaki@localhost test]$ ./Main
3

  我们在实现函数的时候一定要注意有效性检验,这里只做了最基本的检验,在实际开发中要注意的远远要多余此。

strcpy()

  这个函数是字符串复制函数,可以将两个参数中第二个字符串的值完全赋值给第一个字符串,但是要注意第一个字符串一定要有足够大小的空间。接下来提供一种实现思路。

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strcpy(char* destination, const char* source)
{
assert(destination != NULL);
assert(source != NULL);
size_t i;
for(i = 0; i < strlen(source); i++)
{
destination[i] = source[i];

}
destination[i] = '\0';
return destination;
}
int main()
{
char str[10] = {0};
char str2[] = "12314524";
Strcpy(str, str2);
printf("str2 = %s\n", str2);
printf("str = %s\n", str);
}



[misaki@localhost test]$ ./Main
str2 = 12314524
str = 12314524

strcat()

  这个是字符串拼接函数,功能是可以将第二个字符串拼接到第一个字符串的末尾,不过要注意第一个字符串要有足够大的空间。

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>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strcat(char* destination, const char* source )
{
size_t a = strlen(destination);
size_t b = strlen(source) + 1;
for(size_t i = 0; i < b; i++)
{
destination[a] = source[i];
a++;
}
return destination;
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
printf("str = %s, str2 = %s\n", str, str2);
Strcat(str, str2);
printf("拼接后:str = %s, str2 = %s\n", str, str2);
}

[misaki@localhost test]$ ./Main
str = abc, str2 = 12314524
拼接后:str = abc12314524, str2 = 12314524

strcmp()

  这个是字符串比较函数,比较前后两个字符串的大小,如果前一个大于后一个则返回正数,相等返回0,小于返回负数。然而比较字符串的时候则是通过两个字符串从前致后每个字符串的字符的ASCII码的大小来进行比较的,如何相同则比较下一个字符知道出现第一个不相等的字符位置,然后再返回相应的结果。这点从下面的实现代码上可以明确看出。

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
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>                            
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int Strcmp(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
while(*str1 != '\0' && *str2 != '\0')
{
if(*str1 < *str2)
{
return -1;
}
else if(*str1 > *str2)
{
return 1;
}
else
{
++str1;
++str2;
}
}
if(*str1 == '\0' && *str2 != '\0')
{
return -1;
}
if(*str1 != '\0' && *str2 == '\0')
{
return 1;
}
if(*str1 == '\0' && *str2 == '\0')
{
return 0;
}
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
if(Strcmp(str, str2) > 0)
{
printf("str > str2\n");
}
else if(Strcmp(str, str2) == 0)
{
printf("str == str2\n");
}
else
{
printf("str < str2\n");
}
}

[misaki@localhost test]$ ./Main
str > str2

strncpy()

  这个函数与strcpy()类似,不过它的功能增加了可以将指定数量的字符复制到目标字符串中,同时这个函数在一些处理上有一些变化。当我们给出的数字大于原字符串的长度,则目标字符串要用空字符补充多出来长度,如果小于或原字符串的长度则末尾不会自动添加空字符,因此我们尤其要除以这一点,然而这些规则都是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
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strncpy(char* destination, const char* source, size_t num)
{
assert(destination != NULL);
assert(source != NULL);
size_t i = 0;
while(i < num && source[i] != '\0')
{
destination[i] = source[i];
i++;
}
if(i == num)
{
return destination;
}
if(source[i] == '\0')
{
for(; i < num; i++)
{
destination[i] = '\0';
}
return destination;
}
}
int main()
{
char str[1024] = {0};
char str2[] = "12314524";
printf("str2 = %s\n", str2);
Strncpy(str, str2, 3);
printf("复制三个字符后str = %s\n", str);
}



[misaki@localhost test]$ ./Main
str2 = 12314524
复制三个字符后str = 123

strncat()

  strncat()函数与strncpy()函数类似,也是同样加入了可以指定字符数量地将源字符串拼接致目标字符串的末尾。至于其中的特殊情况,比如num参数大于小于或等于原字符串的处理在官方文档上有明确要求和规定,这里不再赘述。

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
#include <stdio.h>                                              
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strncat(char* destination, const char* source, size_t num)
{
assert(destination != NULL);
assert(source != NULL);
size_t i = 0;
while(destination[i] != '\0')
{
i++;
}
size_t j = 0;
while(j < num && source[j] != '\0')
{
destination[i] = source[j];
j++;
i++;
}
destination[i] = '\0';
return destination;
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
printf("str = %s\n", str);
printf("str2 = %s\n", str2);
Strncat(str, str2, 3);
printf("拼接三个字符后:str = %s\n", str);
}



[misaki@localhost test]$ ./Main
str = abc
str2 = 12314524
拼接三个字符后:str = abc123

strncmp()

  strncmp()与strcmp()类似,也是字符串比较函数,同样的也是将第一个字符串中的指定数量的字符与第二个字符串进行比较,返回规则也是完全一致。

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
48
49
#include <stdio.h>                                                          
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int Strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 != NULL);
assert(str2 != NULL);
size_t i = 0;
while(str1[i] == str2[i] && i < num && str1[i] != '\0' && str2[i] != '\0')
{
i++;
}
if(i == num)
{
return 0;
}
if(str1[i] == '\0' && str2[i] == '\0')
{
return 0;
}
if(str1[i] > str2[i])
{
return 1;
}
if(str1[i] < str2[i])
{
return -1;
}
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
int a = Strncmp(str, str2, 3);
int b = Strncmp(str, str2, 0);
printf("str = %s\n", str);
printf("str2 = %s\n", str2);
printf("比较前三个字符串的结果是:%d\n", a);
printf("比较前零个字符串的结果是:%d\n", b);
}



[misaki@localhost test]$ ./Main
str = abc
str2 = 12314524
比较前三个字符串的结果是:1
比较前零个字符串的结果是:0

strstr()

  这个函数的功能相比之前的就较为复杂,是在str1查找为str2的子串。这里的内容与数据结构略有相关,简单来说就是在第一个字符串中找是否存在一串和第二个字符串完全一致的串,如果存在则返回第一次出现的首字符的指针,如果不存在返回NULL。

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
48
#include <stdio.h>                                                       
#include <stdlib.h>
#include <string.h>
#include <assert.h>
const char* Strstr(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
if(*str1 == '\0' || *str2 == '\0')
{
return NULL;
}
const char* black_ptr = str1;
while(*black_ptr != '\0')
{
const char* red_ptr = black_ptr;
const char* sub_ptr = str2;
while(*red_ptr != '\0' && *sub_ptr != '\0' && (*red_ptr == *sub_ptr))
{
++red_ptr;
++sub_ptr;
}
if(*sub_ptr == '\0')
{
//找到了
return black_ptr;
}
++black_ptr;
}
//没找到
return NULL;
}
int main()
{
char str[1024] = "123456";
char str2[] = "234";
const char* str3 = Strstr(str, str2);
printf("str = %s\n", str);
printf("str2 = %s\n", str2);
printf("str2中存在str并打印:%s\n",str3);
}



[misaki@localhost test]$ ./Main
str = 123456
str2 = 234
str2中存在str并打印:23456

strtok()

  这个函数是字符串分割函数,它可以将指定的字符串以指定的字符进行分割,并且随着参数的该表可以记录上次分割结果并且继续上一次分割进度继续进行分割,这个字符串函数使用和实现较为复杂,这里不做细致讲解。

strerror()

  strerror()是错误信息报告函数,根据错误返回的错误码可以将其使用对应的错误信息并且打印。这里举个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>//要使用错误码我们需要引入这个头文件
#include <string.h>
int main()
{
FILE* file;
file = fopen("1.txt", "r");//打开这个文件实际上我的目录下并没有这个文件因此就会产生错误,
if(file == NULL)
{
printf("%s\n", strerror(errno));//打印最后一个错误码的错误信息
}
}




[misaki@localhost test]$ ./Main
No such file or directory

  不难发现打印的错误信息和我们编译错误信息十分相似,是的我们可以通过这种方式提醒用户哪里出现错误,但是目前这种报错方式十分落后。

内存操作函数

  常用的内存操作函数有很多,这里重点介绍两个,并且加以实现。

memcpy()

  这个函数与strncpy()十分类似,不过此时复制的已经不仅仅只能是字符串了,而是任何类型的数据都可以进行复制,最后一个参数num给出的则是目标复制的字节数。

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
#include <stdio.h>                                   
#include <stdlib.h>
#include <assert.h>
void* Memcpy(void* dest, const void* src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char* pdest = (char*)dest;
char* psrc = (char*)src;
for(size_t i = 0; i < num; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
int main()
{
int arr[] = {11,14,26};
int arr2[3];
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr[i]);
}
printf("\n");
Memcpy(arr2, arr, sizeof(arr));
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr2[i]);
}
printf("\n");
}



[misaki@localhost test]$ ./Main
11 14 26
11 14 26

memmove()

  这个函数与memcpy()类似,不过这个函数解决了memcpy()中两个缓冲区重叠可能会导致复制错误的问题,因此相比于memcpy()我更推荐于使用这个函数。

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
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>                          
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
void* Memcpy(void* dest, const void* src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char* pdest = (char*)dest;
char* psrc = (char*)src;
for(size_t i = 0; i < num; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* Memmove(void* dest, const void* src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char* pdest = (char*)dest;
char* psrc = (char*)src;
if(pdest > psrc && pdest < psrc + num)//缓冲区重叠,特殊处理
{
for(int64_t i = num - 1; i >= 0; i--)
{
pdest[i] = psrc[i];
}
}
else
{
Memcpy(dest, src, num);
}
return dest;
}
int main()
{
int arr[] = {11,14,26};
int arr2[3];
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr[i]);
}
printf("\n");
Memmove(arr2, arr, sizeof(arr));
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr2[i]);
}
printf("\n");
}



[misaki@localhost test]$ ./Main
11 14 26
11 14 26

  除了以上这些字符串函数和内存操作函数,在C语言中还有比较常用的字符处理及数学函数函数,都包含在标准库中,这些函数十分简单,容易上手,但是在开发中会给我们带来很多方便,这里不再详细介绍。

【MySQL】第五章-表的约束

发表于 2019-01-26 | 分类于 MySQL
字数统计: 2.6k

第五章 表的约束

  在之前的章节我们有提到过约束,所谓约束是在建表时对表的某一字段进行的特殊限定或者特殊功能附加,在MySQL中有着广泛应用。

空属性

  首先要说的的是在MySQL中,空也用null进行表示,我们可以看到在很多数据暂未赋值时,它们的默认值会为空,不过MySQL中的null与C语言中的NULL有着较大区别。在C语言中空表示着值为0或者不存在值等等意思,而在MySQL中空表示着不知道此处的值,不确定此处的值,并且空不能直接参与运算,因此我们在建表时一般要求值都不为空。因此我们需要在字段后加上not null约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [student]> create table student (
-> id int not null,
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.00 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.02 sec)

  由此即可看出通过我们添加了not null约束,id字段已经要求不能为空了,那么如果我们在添加数据时没有给id字段值会出现什么情况呢?

1
2
MariaDB [student]> insert into student(name) values("张三");
ERROR 1364 (HY000): Field 'id' doesn't have a default value

  当我们在要求id不能为空但是又没有为它设置其他默认值时就会报错。

默认值

  我们可以给某一字段设置默认值,一般情况下默认值为null,要想设置默认值利用default约束即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [student]> create table student (
-> id int not null default 1,
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.01 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | 1 | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

  在此次建表时我们要求id字段不为空,并且默认值设置为1,我们再插入一条数据看看。

1
2
3
4
5
6
7
8
9
10
MariaDB [student]> insert into student(name) values("张三");
Query OK, 1 row affected (0.01 sec)

MariaDB [student]> select * from student;
+----+--------+
| id | name |
+----+--------+
| 1 | 张三 |
+----+--------+
1 row in set (0.00 sec)

  这次我们同样没有给id赋值,但是根据默认值它的值变为了1。

列描述

  所谓描述是添加一种类似于注释一样的约束,实际上不会对字段产生任何影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [student]> create table student (
-> id int not null default 1 comment '编号',
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.08 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | 1 | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

  这样就已经为id字段添加了注释,但是我们发现查看表信息里面却没有显示,这是因为这个注释只会在我们使用查看建表信息时才会显示。

1
2
3
4
5
6
7
8
9
10
MariaDB [student]> show create table student;
+---------+----------------------------------------------------------------+
| Table | Create Table |
+---------+----------------------------------------------------------------+
| student | CREATE TABLE `student` (
`id` int(11) NOT NULL DEFAULT '1' COMMENT '编号',
`name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+---------+----------------------------------------------------------------+
1 row in set (0.01 sec)

  这样我们以后我们可以给每个字段都加上描述,对表字段信息做进一步描述。

zerofill

  这个约束是填充约束,对于字段空位用0进行填充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MariaDB [student]> create table student (
-> id int(5) zerofill default 1 comment '编号',
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.00 sec)

MariaDB [student]> insert into student values(1,"张三");
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> select * from student;
+-------+--------+
| id | name |
+-------+--------+
| 00001 | 张三 |
+-------+--------+
1 row in set (0.00 sec)

  在我们添加zerofill字段后根据我们对字段类型大小对所有空位进行了填充0的操作。

主键

  主键是约束中十分重要的约束,主键作为一种索引,可以用于对数据的排列查找等,因此一个表中最多只能拥有一个主键,同时因为一个键值就唯一了一个数据因此主键不能为空同时不能重复。因此它自带not null约束。我们对有个字段使用primary key约束将其约束为主键,通常主键选做使用int类型的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [student]> create table student (
-> id int(5) primary key default 1 comment '不能为空',
-> name varchar(20) not null comment '姓名'
-> );
Query OK, 0 rows affected (0.02 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(5) | NO | PRI | 1 | |
| name | varchar(20) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

  这样我们就把id字段设置为了主键,同时我们可以将多个字段都设置为主键,成为复合主键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MariaDB [student]> create table student (
-> id int(5) default 1 comment '不能为空',
-> name varchar(20) not null comment '姓名',
-> primary key(id,name)
-> );
Query OK, 0 rows affected (0.01 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(5) | NO | PRI | 1 | |
| name | varchar(20) | NO | PRI | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

  这样name和id字段就形成了一个复合主键。同时如果我们插入主键完全相同的数据就会报错。

1
2
3
4
5
6
7
8
MariaDB [student]> insert into student values(1,'张三');
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> insert into student values(2,'张三');
Query OK, 1 row affected (0.01 sec)

MariaDB [student]> insert into student values(1,'张三');
ERROR 1062 (23000): Duplicate entry '1-张三' for key 'PRIMARY'

  我们也可以删除主键。

1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [student]> alter table student drop primary key;
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(5) | NO | | 1 | |
| name | varchar(20) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

  我们也可以在表建完后再添加主键。

1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [student]> alter table student add primary key(id);
Query OK, 0 rows affected (0.80 sec)
Records: 0 Duplicates: 0 Warnings: 0

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(5) | NO | PRI | 1 | |
| name | varchar(20) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

  主键的操作十分重要,可以帮我们排序数据,查找数据等,是不可或缺的约束之一。

自增长

  当我们想要将我们插入的数据的序号自动增长时我们就可以使用这个约束。并且如果我们想要一个对一个字段使用自增长,这个字段前提必须是一个索引(key一栏有值),并且是整数类型。使用auto_increment添加自增长约束。同时添加了自增长的字段不能再设置默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [student]> create table student (
-> id int primary key auto_increment comment '不能为空',
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.01 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

  这样我就设置完毕了自增长,我们添加几个数据看看。

1
2
3
4
5
6
7
8
MariaDB [student]> select * from student;
+----+--------+
| id | name |
+----+--------+
| 1 | 张三 |
| 2 | 李四 |
+----+--------+
2 rows in set (0.00 sec)

  我们可以看我并不需我们再对id字段赋值,id字段便可以自动从1开始自动对数据进行编号同时我们可以查看目前自增长的数值。

1
2
3
4
5
6
7
8
9
10
11
MariaDB [student]> show create table student;
+---------+---------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+---------+---------------------------------------------------------------------------------------------------------------------------------------+
| student | CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '不能为空',
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 |
+---------+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

  下面的AUTO_INCREMENT=3则表示了目前自增长数值到3了。

唯一键

  往往表中有多个数据需要唯一性,但是主键一个表中只能最多有一个,因此唯一键诞生了。唯一键可以很好的帮助表中各自段的数据确立唯一性,因此被唯一键约束的字段可以为空但并不能重复,其他特性都与主键类似。唯一键使用unique进行约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [student]> create table student (
id int unique
name varchar(20) unique
);
Query OK, 0 rows affected (0.01 sec)

MariaDB [student]> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | YES | UNI | NULL | |
| name | varchar(20) | YES | UNI | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

  对字段进行唯一键约束后如果插入重复数据会如何呢?

1
2
3
4
5
MariaDB [student]> insert into student values(1, '张三');
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> insert into student values(1, '张三');
ERROR 1062 (23000): Duplicate entry '1' for key 'id'

  很明显在这里报错了。

外键

  有时候我们的数据一张表往往是无法完全进行表示的,比如说对于一个学生表,我们要想将其与另外一张班级表进行连接,在他们间建立联系,就要依靠到外键这个约束。比如说接下来这个例子。

1
2
3
4
5
MariaDB [student]> create table class(
-> id int primary key,
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.00 sec)

  首先我们创建了主表,我们将班级表作为主表,这个表要求必须要有主键或唯一键进行约束,也就是说各个数据要求是不重复的,不然从表与之建立联系时会产生矛盾。

1
2
3
4
5
6
7
MariaDB [student]> create table student (
-> id int primary key auto_increment,
-> name varchar(20),
-> class_id int comment '班级id',
-> foreign key (class_id) references class(id)
-> );
Query OK, 0 rows affected (0.00 sec)

  以上建立的这个表我们称之为从表,注意在最后我们加入了外键的语法,使之class_id字段与主表的id字段相互对应。接下来我们插入几个数据看看效果。

1
2
3
4
5
6
7
8
9
10
11
MariaDB [student]> insert into class value(1,'C++'), (2,'java');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
MariaDB [student]> insert into student values(1,'张三',1);
Query OK, 1 row affected (0.01 sec)

MariaDB [student]> insert into student values(2,'李四',2);
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> insert into student values(3,'老王',3);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`student`.`student`, CONSTRAINT `student_ibfk_1` FOREIGN KEY (`class_id`) REFERENCES `class` (`id`))

  从这个例子可以看出我们先给班级表插入了两个班级数据,在从表如果我们正常插入存在对应班级编号数据的学生数据时会正常插入,但是如果我们给老王插入了一个不存在班级id就会报错,同时如果我们给班级id赋值为空也是允许的。

【C语言】第九章-指针进阶

发表于 2019-01-23 | 分类于 C语言进阶
字数统计: 3.2k

第九章 指针进阶

  在初阶篇已经讨论过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
#include <stdio.h>
int main()
{
char str1[] = "HelloWorld";
char str2[] = "HelloWorld";
char* str3 = "HelloWorld";
char* str4 = "HelloWorld";
if(str1 == str2)
{
printf("str1 和 str2 相同!\n");
}
else
{
printf("str1 和 str2 不相同!\n");
}
if(str3 == str4)
{
printf("str3 和 str4 相同!\n");
}
else
{
printf("str3 和 str4 不相同!\n");
}
}

  输出结果:

1
2
3
[misaki@localhost test]$ ./Main
str1 和 str2 不相同!
str3 和 str4 相同!

  由此可见相对于字符数组str1和str2是由两块完全不同的内存空间进行存储,因此他它们的首元素地址并不相同,而对于完全相同的字面量字符串,当有不同的指针指向他们的时候,系统不会再另开辟新空间进行存储,而是会让不同的指针指向同一块字面量字符串存储的内存地址。这是字符数组与字符指针的区别之一。

指针数组和数组指针

  在C语言中,这两种指针是十分容易混淆的,在不同的C语言系列教材中,这两种指针在不同的情境下往往也被冠以其它的名字,例如在多为数组中出现的行指针,但其根源终究不过是这两种指针。

指针数组

  指针数组较为浅显易懂,通过名字也应该能想到,这是一种数组,而数组元素类型是指针。定义语法如下:

        typeName* arrayName[N];

  例:

        int* ptrArray[10] = {NULL};

  这个例子我定义了一个大小为10元素类型为int*型的指针数组。这样的例子看上去很容易理解,但有了这个基础我们就可以定义出以下这几种更有实际用途的指针数组。

1
2
3
4
5
6
char* array[4];
//字符指针数组,里面的每个元素都是一个字符指针,可以用来指向字符或者字符串
//这种指向方式更为简单节省空间,而当我们使用二维数组存储多个字符串往往需要花费更多空间
char** array[5];
//二级字符指针数组,里面的每个元素都是一个指向字符指针的指针,这种用法很不多见,也较为复杂
//但是在我们想要指向多个字符指针数组时就派的上用场。

数组指针

  数组指针相对于指针数组来说较为复杂。数组指针从名字上看不过是一个数组的指针罢了,确实如此。但是定义起来较为难以理解。

        typeName (*arrayName)[N];

  例:

        int (*array)[10];

  当我们定义了一个数组指针后,我们可以将任意一个数组取地址,然后赋值给它。

1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
int array[4] = {0};
int (*arrayPtr)[4] = &array;//注意数组指针定义时[]中的值需与目标数组的长度相同
printf("%p\t%p\n", &array, arrayPtr);
}

  输出:

1
2
[misaki@localhost test]$ ./Main
0x7ffdf4f3c8e0 0x7ffdf4f3c8e0

  通过以上例子我们是否可以联想到我们平时使用的二位数组本身就是一个数组指针呢?

1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
int array[3][4] = {0};
int (*ptr)[4] = array;
printf("%p\t%p\n", array, ptr);
}

  输出:

1
2
[misaki@localhost test]$ ./Main
0x7fffc874b160 0x7fffc874b160

  由此可见二维数组本身就是一个数组指针,用来指向内部的各个一维数组。

  同时在这里还需强调的一点是,又是尽管指针的值相同,也就是说指向同一块内存地址,却有可能指针类型完全不同。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
int arr[3][4] = {0};
printf("%p\n", arr);//二维数组首地址,一维数组指针int (*)[4]
printf("%p\n", &arr);//二维数组指针int (*)[3][4]
printf("%p\n", arr[0]);//一维数组首地址,int型指针int*
printf("%p\n", &arr[0][0]);//二维数组第一个元素的地址,int型指针int*
}

  输出:

1
2
3
4
5
[misaki@localhost test]$ ./Main
0x7ffca85b9440
0x7ffca85b9440
0x7ffca85b9440
0x7ffca85b9440

  由以上的代码可以看出尽管几个指针类型不尽相同,但它们都指向同一块内存空间,因此我们在给指针间进行赋值时,一定要清楚各个指针的类型,在这里十分容易出错。

延伸

  有了数组指针与指针数组的基础,我们即可将他们合并得出更多更加复杂的指针,例如数组指针数组:

        int (*ptr[10])[4];

  以上这行代码表示定义了一个长度为10的数组,数组内每一个元素都是一个数组指针,其中每个指针指向长度为4的int型数组。有了数组指针数组,那么是否就有指针数组指针呢?是的,得益于C语言中灵活多变的语法,这些都可以得到实现,但是难度较高也并不经常使用,在此不再讨论。

指针与数组传参

  在C语言中,我们都知道对于函数传递参数都是进行副本传参,也就是将实参克隆一份变为形参再进行传递,由此形参的改变并不会影响到实参。但是当我们传递指针的时候就可以对原本指针所指向的变量进行改变。而当我们想要把数组传递进函数中时,无论我们以何种语法进行传递,对于编译器来说也都会自动帮我们将数组转换为指针继续宁传入,以此来节省开销。

一维数组传参

  A:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
void Print(int* arr, int len)
{
for(int i = 0; i < len; i++)
{
printf("%d\n", arr[i]);
}
}
int main()
{
int arr[4] = {1, 2, 3, 4};
Print(arr, 4);
}

  B:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>  
void Print(int arr[], int len)
{
for(int i = 0; i < len; i++)
{
printf("%d\n", arr[i]);
}
}
int main()
{
int arr[4] = {1, 2, 3, 4};
Print(arr, 4);
}

  对于一维数组来说,我们可以直接使用指针的方式进行传入,也可以使用数组作为形参进行传入,并且以数组作为形参的时候可以直接忽略数组的长度,因为无论如何编译器都会将其转换为指针,这里的长度也就不需要了,因此为了更好的表示数组的长度,我们也必须将数组的长度作为参数也传入函数。

二维数组传参

  A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
void Print(int arr[][4], int len)
{
for(int i = 0; i < len; i++)
{
for(int j = 0 ; j < 4; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
Print(arr, 3);
}

  B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
void Print(int (*arr)[4], int len)
{
for(int i = 0; i < len; i++)
{
for(int j = 0 ; j < 4; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
Print(arr, 3);
}

  对于二维数组来说,也同样的有两种方法,一种是以数组的方式,另一种是以与之相同类型的数组指针的方式,不过这里不同的是,二维数组的低维度不可省略,而高维度可以省略。

  对于数组传参不光是一维和二维数组,几乎所有类型的数组都适用同样的原理。

函数指针

  函数指针是指针中较为重要也是比较特殊的类别,并且在实际使用中也是极为常见的类型。

函数指针

  函数指针定义:

        void (*FunName)();

  函数指针定义起来语法十分简单,而我们想要函数指针指向某个函数则只需要让它指向和它返回值类型以及参数完全相同的函数即可。当他进行指向后我们就可以使用函数指针直接进行函数的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
void Print(int (*arr)[4], int len)
{
for(int i = 0; i < len; i++)
{
for(int j = 0 ; j < 4; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
Print(arr, 3);
void (*ptr)(int (*arr)[4], int len) = Print;
ptr(arr, 3);
(*ptr)(arr, 3);
}

  输出:

1
2
3
4
5
6
7
8
9
10
[misaki@localhost test]$ ./Main
1 2 3 4
5 6 7 8
9 10 11 12
1 2 3 4
5 6 7 8
9 10 11 12
1 2 3 4
5 6 7 8
9 10 11 12

  在以上的例子中我们使用了一个ptr函数指针指向了Print()函数,注意在给函数指针赋值时,函数名就是函数的地址了,而在使用函数指针进行函数调用时,则可以直接对指针像函数呢样传参即可,当然也可以先解引用再传参,都是可以的。

函数指针数组

  接下来我们将函数指针与我们之前说过的指针数组相结合,形成函数指针数组,这在实际开发中往往可以大大提高执行效率。

  与数组指针数组类似有:

        
int (*arr[10])(参数)

  这就是一个函数指针数组的标准定义式了,以下我举个小小例子将其带入应用。

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 Plus(int num1, int num2)
{
return num1 + num2;
}
int Min(int num1, int num2)
{
return num1 - num2;
}
int main()
{
int (*arr[2])(int, int) = {
Plus,
Min
};
int choice = 0, num1, num2;
printf("执行加法请输入1,执行减法请输入2:");
scanf("%d", &choice);
if(choice == 0)
{
return 0;
}
printf("请输入操作数:");
scanf("%d%d", &num1, &num2);
printf("%d\n", arr[choice - 1](num1, num2));
}

  这里我简单举了个以加减法为主的小计算器的例子,在最后我们使用函数指针数组进行函数调用以此减少判断的开销,或许这个例子不够明显,但在我们有很多函数要进行调用时,结果就会更加一目了然了。

回调函数

  回调函数说简单些就是我们将函数指针作为参数传给了另外一个函数,这个函数指针所指向的函数往往不是实现者所写的,因此更不会由实现者进行调用,而是由使用者根据使用情况写出的自定义函数,这个函数在特定情况下进行调用。这就是回调函数,因此这样的函数在编写外部接口时经常被用到。接下来我i我们以一个qsort函数为例编写一个回调函数并进行使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int cmp(const void* p1, const void* p2)//自己所编写的函数
{
return (*(int*)p1 > *(int*)p2);
}
int main()
{
int arr[] = {11, 22, 99, 34, 454, 32, 0};
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), cmp);//传入函数中成为回调函数
for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d\n", arr[i]);
}
}

  输出:

1
2
3
4
5
6
7
8
[misaki@localhost test]$ ./Main
0
11
22
32
34
99
454

  从以上这个例子即可看出回调函数的威力,我们可以根据使用我们自己所写的函数改变排序规则。因此回调函数也是实际使用中十分重要的一环。

【C语言】第八章-数据的存储

发表于 2019-01-21 | 分类于 C语言进阶
字数统计: 2.2k

第八章 数据的存储

  从本章开始就正式进入了C语言进阶篇的学习,我们将从更为底层的角度思考C语言中更为复杂的机制和语法。

第一节

基本数据类型

  C语言中的基本数据类型在初阶篇已经探讨过了,分为整形,浮点型,字符型与构造类型几个大类,每个类型都有着不同的容量,所占的内存大小也不尽相同,有的甚至在不同系统下都有着不同的大小因此必须在实核情境下选择合适的数据类型进行使用。

1
2
3
4
5
6
7
char        //字符数据类型,占1字节
short //短整型,占2字节
int //整形,占4字节
long //长整型,占4字节
long long //更长的整形,占8字节
float //单精度浮点数,占4字节
double //双精度浮点数,占8字节

  其中整形数组类型分为有符号和无符号类型,无符号类型不能表示负数,否则会导致很严重的数据出错问题,关于为什么出错与数据在内存中的存储有很大的关系,之后我们会进行解释。

整形在内存中的存储

大端字节序、小端字节序

  数据在内存中的存储形式都是以二进制数的方式所表示的,比如说我们要将int型8这个十进制数存入内存,编译器会帮助我们将其转换为二进制形式00000000 00000000 00000000 00001000,一共32位,这里我们先不探讨数据在内存中是怎么转换为二进制形式进行存储的。这32位二进制一共是4个字节,系统会以每个字节为一个单位继续宁内存存储,因此为了方便起见,我们往往将内存中的数据以十六进制进行表示,因为1位十六进制数等于4位二进制数,也就是一个字节原本需要8位二进制表示,现在只需要用2位十六进制进行表示即可,通常我们在内存监视器中所看到的数据存储也大多是以十六进制进行表示的。将这么一串二进制转换为八进制即可表示为00 00 00 08。但是实际在内存中是以二进制进行存储的,在这里用十六进制是为了方便我们进行观察。

  之后就要将这么一串数据存入内存中了,但是就在这里问题出现了,我们是要将最高位1个字节的数据首先存储在低地址区,还是要将最低位1个字节的数据首先存储在低地址区呢?其实这两种存储方式都得到了实施,而具体的存储方式与我们电脑的CPU密切相关(其实也并不仅有CPU决定,具体来说是和环境也就是平台密切相关,但CPU占大头),因此在当前的市场就有了大小端字节序之称,专门用来用来区分这两种不同的数据在内存中的存储方式。

  1、大端字节序:低位存储在高地址上,依然以以上的例子为例,则在内存中以这样的方式进行存储:00 00 00 08。

  2、小端字节序:低位存储在低地址上,依然以以上的例子为例,则在内存中以这样的方式进行存储:08 00 00 00。

  注意:大小端字节序仅仅决定数据在内存中的存储,并不会因此改变数据本身的值!!!
  有了以上的原理,我们即可使用代码验证我们当前的环境是大端还是小端了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main()
{
int a = 0x11223344;
char* p = (char*)&a;
if(*p == 0x11)
{
printf("大端!\n");
}
else
{
printf("小端!\n");
}
}

  通过以上的代码,我们将内存中的数据取头一个字节的数据进行验证的方法即可判断是大端还是小端。

整形在内存中的存储

  接下来就是数据是如何转换为二进制并且在内存中进行储存的问题了。首先是整型是如何在内存中进行存储的呢?这就要牵扯到原码,补码,反码三个紧密联系的二进制码了。

  计算机在内存中都是以补码的形式进行存储的,而我们直接将一个数转换成二进制的所形成的最多只能被称为原码其重要进行两步转换。

有符号整形

  这里我们先以有符号进行演示,之后再讨论无符号。这次我们以(int)-8为例,在有符号的情况下,我们的数据会将第一位二进制留出来作为符号位,0表示正数,1表示负数,这也是为什么32位的整形在有符号的情况下最大值只能到21亿的原因,因为有一位被保留出来做了符号位。除过第一位我们将数据原本的值转换为二进制即可表示为10000000 00000000 00000000 00001000,这段代码即被称为原码。之后我们想要转换为补码还得先将其转换为反码,就是在原码的基础上,除过符号位外全部按位取反即可变为11111111 11111111 11111111 11110111这就是反码。之后我们只需再将反码 +1即可变为补码11111111 11111111 11111111 11111000这就是实际上有符号整形在内存中的存储形式了。

  而以上的转换过程只对负整数适用,而正整数的原反补码都相同。

无符号整形

  无符号整形与有符号整形基本类似,只是其省略了最高位的符号位而已,因此使得无符号的最大值比有符号整形最大值大了一倍,但也因此使得无符号整型无法表示负数。

  但是在这里值得强调的一点是,一个数据存储在内存中进行存储,尽管存储的形式完全一致,内容也完全一致,但是我们使用不同的理解方法去理解,就会有完全不同的结果。这点我们从以下这个例子就可以看出来。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
unsigned int a = -1;
printf("%u\n", a);
}

[misaki@localhost test]$ ./Main
4294967295

  从执行结果就可以看出其中的问题,在存储数据的时候我们虽然定义了一个无符号的整形,但是当我们存储负数时编译器也并不会报错,此使-1会按照有符号负整数的方式进行存储在内存中,11111111 11111111 11111111 11111111,但是当我们使用无符号将其进行打印时,编译器又会将此数据以无符号正整数的方式将其编译转换成十进制就得出了我们最终的结果,因此可以看出同一个数据有多种理解方式因此得出的结果也不尽相同,并且无符号数据类型同时也是十分危险的,十分容易出错,因此我并不推荐经常使用它们。

  但是说了这么多我们为什么要如此大费周章使用补码存储数据呢?这又与我们电脑的cpu密切相关
了,由于我们的cpu只有加法器,我们想要更为方便与简单的处理减法以及表示负数我们就需要想出一种最为合理和简单方式,因此补码诞生了,这使得cpu可以将符号域与数值域共同参与运算,大大提高了运算效率,因此将数据存储为补码是必要的。

浮点型在内存中的存储

  关于浮点型在内存中的存储十分复杂,在这里只作简单讲解。

  首先浮点型在内存中的存储与整形可以说是天差地别。根据国际标准IEEE的规定,任意一个二进制浮点数可以表示为以下的形式(-1) ^ S * M * 2 ^ E,然而其中E和M的大小共同决定着这个数据的精度,而double之所以比float进度更高也是因为其在数据存储域的划分上有着更多的存储空间,关于其中的细节,还有着十分严密甚至是严苛的规定和标准,具体请参考IEEE 754。

【MySQL】第四章-数据类型

发表于 2019-01-20 | 分类于 MySQL
字数统计: 2k

第四章 数据类型

数据类型及分类

  在MySQL中有着类似于C语言的各种数据类型,他们分别有着不同的大小,适用于不同的数据表示情况。

数据类型

数值类型

数据类型

  在数值类型中与C语言基本类似,每个类型都有有符号与无符号两种,表示范围也在上表中有所描述,接下来我挑几个比较特殊的类型进行讲解。

tinyint类型

  tinyint能表示刚好一个字节大小的数值,与c语言中char类型十分类似,不过在MySQL中此类型作为数值类型而非字符类型出现。接下来看看MySQL中对于数值越界的表现。

1
2
3
4
5
6
7
MariaDB [student]> create table test 
(
id tinyint
);

MariaDB [student]> insert into test values(128);
ERROR 1264 (22003): Out of range value for column 'id' at row 1

  很明显,MySQL提醒我们我们所插入的数值越界了。

  接下来我们定义一个无符号的tityint类型,并插入一个负数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MariaDB [student]> create table test 
-> (
-> id tinyint unsigned
-> );
Query OK, 0 rows affected (0.01 sec)

MariaDB [student]> desc test;
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| id | tinyint(3) unsigned | YES | | NULL | |
+-------+---------------------+------+-----+---------+-------+
1 row in set (0.00 sec)

MariaDB [student]> insert into test values(-1);
ERROR 1264 (22003): Out of range value for column 'id' at row 1

  在这里对无符号插入负数也会显示越界。在MySQL中可以指定有符号与无符号,默认是有符号的。

bit类型

  bit(N)类型能表示任意位大小的数值,N的数值是1-64如果忽略N则默认是1。

  bit最为特殊的地方是它在显示的时候默认是按照字符型进行显示的,这和其他数值类型都不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MariaDB [student]> create table test 
-> (
-> id int,
-> Bit bit(8)
-> );
Query OK, 0 rows affected (0.11 sec)

MariaDB [student]> insert into test values(10, 10), (65, 65);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0

MariaDB [student]> select * from test;
+------+------+
| id | Bit |
+------+------+
| 10 | |
| 65 | A |
+------+------+
2 rows in set (0.00 sec)

  可以发现bit类型为10的显示无法出现,而65出现了A,由此则可以说明bit类型特殊的显示方式。同时,如果一个字段只需要显示0或1用bit(1)可以大大的减少空间。

float类型

  float(M, D),M表示显示的长度,D表示小数的位数,MySQL会自动四舍五入。占用空间四个字节。

1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [student]> insert into test values(99.991), (99.99);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0

MariaDB [student]> select * from test;
+--------+
| salary |
+--------+
| 99.99 |
| 99.99 |
+--------+
2 rows in set (0.00 sec)

  MySQL自动将我们的数据多余的额不服进行四舍五入后再进行存储。

  同时float虽然是小数类型但是也同样拥有无符号类型。float(4,2)的范围就是0 ~ 99.99。

decimal类型

  decimal也是小数类型,其大体与float类似,但是decimal可以表示更大的精度,类似于C语言中的double型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MariaDB [student]> create table test 
-> (
-> salary decimal(10,8),
-> salary2 float(10,8)
-> );
Query OK, 0 rows affected (0.00 sec)

MariaDB [student]> insert into test values(12.12345678, 12.12345678);
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> select * from test;
+-------------+-------------+
| salary | salary2 |
+-------------+-------------+
| 12.12345678 | 12.12345695 |
+-------------+-------------+
1 row in set (0.01 sec)

  因此我们再要表示精度更高的数据时往往使用decimal类型。

  float表示的精度大约是7位。
decimal整数最大位数M为65。支持小数最大位数D是30。如果D被省略,默认为0。如果M被省略,默认是
10。

字符串类型

  注意在MySQL中字符串用''或者""标记均可,不做区分

char类型

  char(L)为固定长度字符串类型,最大长度可以是255。意思是说char类型的字段存放数据最多只能放255个字母或者汉字,这点与C语言有很大区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MariaDB [student]> create table test 
-> (
-> name char(256)
-> );
ERROR 1074 (42000): Column length too big for column 'name' (max = 255); use BLOB or TEXT instead
MariaDB [student]> create table test ( name char(255) );
Query OK, 0 rows affected (0.01 sec)

MariaDB [student]> insert into test values('张三');
Query OK, 1 row affected (0.08 sec)

MariaDB [student]> select * from test;
+--------+
| name |
+--------+
| 张三 |
+--------+
1 row in set (0.00 sec)

varchar类型

  varchar(L)为可变长度字符串类型,最大长度为65535。
\
  其实varchar最大长度为多少与字符编码密切相关,varchar长度可以指定为0到65535之间的值,但是有1 - 3个字节用于记录数据大小,所以说有效字节数是65532。当我们的表的编码是utf8时,varchar(n)的参数n最大值是65532/3=21844[因为utf中,一个字符占用3个字
节],如果编码是gbk,varchar(n)的参数n最大是65532/2=32766(因为gbk中,一个字符占用2字节)。

1
2
MariaDB [student]> create table test ( name varchar(21845) )charset=utf8;
ERROR 1074 (42000): Column length too big for column 'name' (max = 21844); use BLOB or TEXT instead

  以上代码验证了utf8下确实长度不能超过21844。

  同时char与varchar之间关于定长和变长有着很大的区别。
数据类型

  如上表所示,通常我们在使用char类型时,定义多长的长度,实际使用时不会改变大小,但是varchar不同,会根据实际存储数据大小进行自动调节和变更,但是会多占用一个字节的大小,同时还很吃效率,因此实际使用中选择合适的类型是至关重要的。

日期类型和时间类型

  常用的时间类型有以下三个。

  datetime 时间日期格式 ‘yyyy-mm-dd HH:ii:ss’ 表示范围从1000到9999,占用八字节。

  date:日期 ‘yyyy-mm-dd’,占用三字节。

  timestamp:时间戳,从1970年开始的 yyyy-mm-dd HH:ii:ss格式和datetime完全一致,占用四字节。

  时间戳我们在C语言中也已经有所接触了,可以帮助我们自动显示当前的时间,但是时间戳也有大小限制。(貌似目前已经快到极限了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MariaDB [student]> create table test
-> (
-> d1 date,
-> d2 datetime,
-> d3 timestamp
-> );
Query OK, 0 rows affected (0.01 sec)
MariaDB [student]> insert into test(d1,d2) values('1999-1-20', '1999-1-20 1:0:0');
Query OK, 1 row affected (0.01 sec)

MariaDB [student]> select * from test;
+------------+---------------------+---------------------+
| d1 | d2 | d3 |
+------------+---------------------+---------------------+
| 1999-01-20 | 1999-01-20 01:00:00 | 2019-01-19 09:51:26 |
+------------+---------------------+---------------------+
1 row in set (0.00 sec)

  时间戳会自动帮我们补齐,取当前系统时间。

enum和set类型

  enum枚举类型,用于多选一。最多可添加65535个选项。

  set集合类型,用于多选多。最多可添加64个选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MariaDB [student]> create table test
-> (
-> username varchar(30),
-> hobby set('游戏','追番','搞比利'),
-> gender enum('男','女')
-> );
Query OK, 0 rows affected (0.04 sec)

MariaDB [student]> insert into test values('张三','搞比利,追番','男');
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> insert into test values('李四','游戏,追番',2);
Query OK, 1 row affected (0.00 sec)

MariaDB [student]> select * from test;
+----------+------------------+--------+
| username | hobby | gender |
+----------+------------------+--------+
| 张三 | 追番,搞比利 | 男 |
| 李四 | 游戏,追番 | 女 |
+----------+------------------+--------+
2 rows in set (0.00 sec)

  我们在给enum和set类型添加数据时可以选择使用数字选择选项的方式进行添加,但是不建议这种方式,因为十分不利于代码可读性。

1
2
3
4
5
6
7
MariaDB [student]> select * from test where hobby = '追番,搞比利';
+----------+------------------+--------+
| username | hobby | gender |
+----------+------------------+--------+
| 张三 | 追番,搞比利 | 男 |
+----------+------------------+--------+
1 row in set (0.00 sec)

  我们同时可以使用以上方式找到集合中指定选项的所有数据。还可以利用find_in_set()函数进行查询。

1
2
3
4
5
6
7
8
MariaDB [student]> select * from test where find_in_set('追番',hobby);
+----------+------------------+--------+
| username | hobby | gender |
+----------+------------------+--------+
| 张三 | 追番,搞比利 | 男 |
| 李四 | 游戏,追番 | 女 |
+----------+------------------+--------+
2 rows in set (0.01 sec)

  由此即可查询出所有hobby集合中包含追番的数据。

【MySQL】第三章-表的操作

发表于 2018-12-23 | 分类于 MySQL
字数统计: 1.5k

第三章-表的操作

创建表

基本语法

1
2
3
4
5
CREATE TABLE table_name (
field1 datatype,
field2 datatype,
field3 datatype
) character set 字符集 collate 校验规则 engine 存储引擎;

  在库中创建表是数据库最为基础的操作,在创建表过程中我们也可以对表进行一些自定义,但是大家尽可以放心的去创建表,对于表一的字段和一些属性在表创建成功也是可以更改的,就算我们创建错了表,也不要害怕,大不了就是删表重建罢了,最为主要的是不断的练习与尝试。

示例

1
2
3
4
5
create table student 
(
id int primary key auto_increment,
name varchar(20)
);

  关于以上的指令的要求是创建一个名为student的表,表中包含int的id和varchar()的name两个字段,并且约束id字段为主键,设置自增长(关于表的约束后面章节会有讲解)。

查看表

查看表结构

语法

  desc 表名;

  用此指令用于查看表的详细结构。

示例

1
desc student;

  结果:

1
2
3
4
5
6
7
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.26 sec)

  可以看到如我们所愿打印出了表的详细结构。一共分为了几列。其中Field列显示字段名;Type列显示字段类型;Null列显示是否可以为空(由于我将id设置为了主键因此不可以为空了);Key列显示键的类型,没有则不显示;Default列显示默认值,未设置则默认值为空;Extra显示其他约束。

查看表的创建语句

语法

  show create table 表名;
  与查看库的创建语句类似,但是所显示的信息却有很大变化。

示例

1
show create table student;

结果:

1
2
3
4
5
6
7
8
9
10
+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| student | CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

  从表的创建语句中我们可以看到表的名称,表的字段创建信息包括字段类型大小以及字符集和校验值等。

修改表

  在实际开发过程中我们最初所创建的表往往要随着需求的变更而进行改变,然而每次变更需求都重新建立新表未免显得麻烦,因此修改表便成了数据库中不可或缺的操作。

添加字段

语法
1
ALTER TABLE tablename ADD (column datatype [DEFAULT expr][,column datatype]...);

  通过以上指令即可在已建成的表中添加新的字段,并且可以做到在任意字段后进行添加。

示例
1
alter table student add class varchar(20) after id;

结果:

1
2
3
4
5
6
7
8
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| class | varchar(20) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

  可以看出我们通过指令在id后添加了一个新的字段class,并且类型为carchar(20)。在指令中,after则就表示插入在哪个字段之后,可以任意选择,如果不写此选项,默认插在尾部。

修改字段属性

语法
1
ALTER TABLE tablename MODIfy (column datatype [DEFAULT expr][,column datatype]...);

  通过以上指令即可对字段的属性进行更改,包括字段的类型,大小,约束等都可以修改。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
MariaDB [db1]> desc student;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| class | varchar(20) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

MariaDB [db1]> alter table student modify class varchar(20) default '20';
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0

MariaDB [db1]> desc student;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| class | varchar(20) | YES | | 20 | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

  这个例子中我修改了class字段的默认值,前后可以看出来我将默认值修改为了20。这个指令还可以修改很多其他属性。

更换字段

语法
1
alter table 表名 change 字段名 新字段名 定义; --新字段需要完整定义

  这个指令可以将字段更换为新的字段,新的字段需要进行完整的定义,包括字段的类型大小和各种约束等等。

示例
1
alter table student change class banji varchar(20) default '20';

结果:

1
2
3
4
5
6
7
8
desc student;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| banji | varchar(20) | YES | | 20 | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+

  这个例子中我将class字段更换为banji字段,并且重新设置类型为varchar(20)默认值为20。

更改表名

语法
1
alter table 表名 rename 新表名;

  这个指令可以将表进行改名。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MariaDB [db1]> show tables;
+---------------+
| Tables_in_db1 |
+---------------+
| student |
+---------------+
1 row in set (0.00 sec)

MariaDB [db1]> alter table student rename xuesheng;
Query OK, 0 rows affected (0.01 sec)

MariaDB [db1]> show tables;
+---------------+
| Tables_in_db1 |
+---------------+
| xuesheng |
+---------------+
1 row in set (0.00 sec)

  这个例子中我将student表名更改为了xuesheng。

删除字段

语法
1
ALTER TABLE tablename DROP (column);

  这个指令即可删除字段,不过删除字段要谨慎小心,因为删除字段失去的数据不可恢复,除非事先备份过。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MariaDB [db1]> desc xuesheng;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| banji | varchar(20) | YES | | 20 | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

MariaDB [db1]> alter table xuesheng drop banji;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0

MariaDB [db1]> desc xuesheng;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

  在这个例子中我删除了表xuesheng中的banji字段。

删除表

语法

1
DROP [TEMPORARY] TABLE [IF EXISTS] tbl_name [, tbl_name] ...

  删除整张表,千万慎用,数据无法恢复,除非事先备份。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
MariaDB [db1]> show tables;
+---------------+
| Tables_in_db1 |
+---------------+
| xuesheng |
+---------------+
1 row in set (0.00 sec)

MariaDB [db1]> drop table xuesheng;
Query OK, 0 rows affected (0.00 sec)

MariaDB [db1]> show tables;
Empty set (0.00 sec)

  在这个例子中我删除了xuesheng表,删表和删库一样需谨慎!

  以上就是关于表的所有基本操作,熟能生巧。

【MySQL】第二章-库的操作

发表于 2018-12-21 | 分类于 MySQL
字数统计: 1.5k

第二章 库的操作

  注意接下来几章大多数的指令和操作都是在数据库中进行的。从Linux进入MySQL的指令为mysql -uroot -p之后输入密码即可进入。使用quit即可退出

创建数据库

  语法:[]为可省略项

1
2
3
4
5
CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification]
...]
create_specification:
[DEFAULT] CHARACTER SET charset_name
[DEFAULT] COLLATE collation_name

  在这里有几点需要说明的是在创建数据库的语法中,附加选项collate表示的是校验规则,这个对字符排序有一些影响,我们后面会详细讲解。character表示的是字符编码规范。我们知道常用的编码规范有UTF-8,GB2312,GBK等等,不同的编码格式对字符的处理方式不同,而如果用不符的编码规范去打开别的编码规范的文本文件则会造成乱码,这是很我们经常会遇到的麻烦之一,但是有一种通用的变长编码,虽然所占空间可能会更多但是也使得编码更加方便容易,能减少很多麻烦,这种规范就是UTF-8,我们在安装MariaDB的时候所做的配置中就已经将数据库默认的编码规范更改为UTF-8了,因此我们在编码规范上也就不用做其他设置,使用默认的UTF-8即可。如果需要使用别的编码也只需要在建库的时候加上character set charaset_name语句即可。

  创建实例:

  1、create database db1;:创建一个校对规则和字符规范全为默认值的数据库db1。

  2、create database db2 charset=utf8;:创建一个使用utf-8字符集,校对规则默认的数据库db2。

  3、create database db3 charset=utf8 collate utf8_general_ci;:创建一个使用utf-8校对规则为utf8_general_ci的数据库db3。

字符集和校验规则

查看系统默认字符集及校验规则

  1、show variables like 'character_set_database';:查看系统默认的字符集。

  2、show variables like 'collation_database';:查看系统默认校验规则。

查看系统支持的字符集及校验规则

  1、show charset;:查看系统支持的字符集。

  2、show collation;:查看系统支持的校验规则。

校验规则对数据库的影响

  1、首先我们分别创建两个库分别使用不同的校验规则utf8_general_ci和utf8_bin分别命名为db1和db2。

1
2
create database db1 collate utf8_general_ci;
create database db2 collate utf8_bin;

  2、向数据库db1中创建一个表,并且存入相应的数据。

1
2
3
4
5
6
use db1;
create table person
(
name varchar(20)
);
insert into person values('a'),('A'),('b'),('B');

  对数据库db2执行同样的操作。

1
2
3
4
5
6
use db2;
create table person
(
name varchar(20)
);
insert into person values('a'),('A'),('b'),('B');

  3、对数据库db1中的数据进行查询,根据name进行排序。

1
2
use db1;
select * from person order by name;

  结果如下:

1
2
3
4
5
6
7
8
+------+
| name |
+------+
| a |
| A |
| b |
| B |
+------+

  对数据库db2进行同样的查询。

1
2
use db2;
select * from person order by name;

  结果如下:

1
2
3
4
5
6
7
8
+------+
| name |
+------+
| A |
| B |
| a |
| b |
+------+

  可以看出同样的操作因为校验规则的不同,导致查询的排序结果不同,这是因为一个排序区分大小写,而另一个不区分所造成的,为了达到统一,我们统一默认使用utf8_general_ci的校验规则。

操作数据库

查看当前数据库

  show databases;:查看当前已有的数据库。

  示例:

1
2
3
4
5
6
7
8
9
10
MariaDB [Misaki]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| Misaki |
| mysql |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

显示创建语句

  show create database 数据库名。

  示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MariaDB [Misaki]> show create table hero;
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| hero | CREATE TABLE `hero` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(20) NOT NULL COMMENT '名称',
`hp` int(11) DEFAULT NULL COMMENT '血量',
`mp` int(11) DEFAULT NULL COMMENT '蓝条',
`describle` varchar(50) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

修改数据库

  语法:

1
2
3
4
5
ALTER DATABASE db_name
[alter_spacification [,alter_spacification]...]
alter_spacification:
[DEFAULT] CHARACTER SET charset_name
[DEFAULT] COLLATE collation_name

  对数据库的修改主要是修改字符集和校验规则。

  实例:

1
2
MariaDB [Misaki]> alter database Misaki charset=utf8;
Query OK, 1 row affected (0.00 sec)

  将Misaki数据库的字符集改为utf-8。

数据库删除

  语法:DROP DATABASE [IF EXISTS] db_ name;

  示例:

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
MariaDB [Misaki]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| Misaki |
| db1 |
| mysql |
| performance_schema |
+--------------------+
5 rows in set (0.00 sec)

MariaDB [Misaki]> drop database db1;
Query OK, 0 rows affected (0.00 sec)

MariaDB [Misaki]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| Misaki |
| mysql |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

数据库备份和删除

  以下操作前#表示在退出MySQL在Linux下执行,mysql表示在MySQL下执行。

数据库备份

  语法:# mysqldump -u root -p 数据库名 > 数据库备份存储的文件路径

  示例:# mysqldump -u root -p db1 > mytest1.sql

  之后即可发现目录下多出了mytest1.sql的文件,这个文件就存储了数据库的有关信息。

数据库恢复

  语法:# mysql -u root -p 数据库名 < 数据库备份存储的文件路径

  示例:# mysql -u root -p db1 < mytest1.sql

  以上这种数据库备份及恢复的方法可适用于大多数情况,不过在恢复前需要在数据库中先建立一个空库用于恢复备份。数据库备份还有很多种方式,但是推荐使用这种,因为更为方便和便于使用。

查看连接情况

  show processlist。

  可以告诉我们有哪些用户连接到了我们的MySQL,如果查出有哪个用户不是正常登录,则数据库有可能被入侵了。

  以上就是关于库的基本操作,多操作才能完全掌握,熟能生巧。

【MySQL】第一章-数据库基础

发表于 2018-12-16 | 分类于 MySQL
字数统计: 1.3k

第一章 数据库基础

什么是数据库

  数据库是程序开发中十分重要且关键的一环,是十分重要也是必要的用来存储数据的方式和手段。和普通的在内存中存储不同,数据库可以将大量的数据更加方便的和快捷地进行管理。因此数据库水平是衡量一个程序员水平的重要指标。

  目前主流的数据库有以下几个。

  1、SQL Server。

  2、Oracle。

  3、MySQL。

  4、PostgreSQL。

  ……

  而本博选用一种在Linux下开源的控制台版本的数据库:MariaDB。此款数据库是MySQL的一个分支,是MySQL之父在MySQL闭源之后重新依据MySQL制作的开源版本,并以自己女儿Maria的名字进行命名。其实与其叫MariaDB为MySQL的替代品,不如称之为升级版本。因为MariaDB在性能上对MySQL多多少少都有所提升,并且还对一些方面的技术有所创新。除此之外MariaDB与MySQL的语法几乎完全一致(其实大多数据库的语法都是相似的,几乎没有很大的区别),也是为了熟悉Linux下的控制台操作我选做MariaDB为例进行学习。

安装及配置

  以下的所有操作均是在root用户下执行。

  1、安装MariaDB服务。

  yum install -y mariadb-server

  2、安装 mariadb 命令行客户端

  yum install -y mariadb

  3、安装 mariadb C library

  yum install -y mariadb-libs

  4、安装 mariadb 开发包

  yum install -y mariadb-devel

  5、更改相关配置

  用vim打开/etc/my.cnf.d/client.cnf文件,vim /etc/my.cnf.d/client.cnf。[client] 下加一行配置 default-character-set=utf8。

  最终内容

1
2
3
4
5
6
7
8
9
10
11
12
13
#
# These two groups are read by the client library
# Use it for options that affect all clients, but not the server
#


[client]
default-character-set = utf8

# This group is not read by mysql client library,
# If you use the same .cnf file for MySQL and MariaDB,
# use it for MariaDB-only client options
[client-mariadb]

  用vim打开/etc/my.cnf.d/mysql-clients.cnf文件,vim /etc/my.cnf.d/mysql-clients.cnf。[mysql] 下加一行配置 default-character-set=utf8。

  最终内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
# These groups are read by MariaDB command-line tools
# Use it for options that affect only one utility
#

[mysql]
default-character-set = utf8

[mysql_upgrade]

[mysqladmin]

[mysqlbinlog]

[mysqlcheck]

[mysqldump]

[mysqlimport]

[mysqlshow]

[mysqlslap]

  用vim打开/etc/my.cnf.d/server.cnf文件,vim /etc/my.cnf.d/server.cnf。[mysqld] 下加配置

1
2
3
4
5
6
7
collation-server = utf8_general_ci

init-connect='SET NAMES utf8'

character-set-server = utf8

sql-mode = TRADITIONAL

  最终内容

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
#
# These groups are read by MariaDB server.
# Use it for options that only the server (but not clients) should see
#
# See the examples of server my.cnf files in /usr/share/mysql/
#

# this is read by the standalone daemon and embedded servers
[server]

# this is only for the mysqld standalone daemon
[mysqld]
collation-server = utf8_general_ci
init-connect = 'SET NAMES utf8'
character-set-server = utf8

sql-mode = TRADITIONAL

# this is only for embedded server
[embedded]

# This group is only read by MariaDB-5.5 servers.
# If you use the same .cnf file for MariaDB of different versions,
# use this group for options that older servers don't understand
[mysqld-5.5]

# These two groups are only read by MariaDB servers, not by MySQL.
# If you use the same .cnf file for MySQL and MariaDB,
# you can put MariaDB-only options here
[mariadb]

[mariadb-5.5]

  使用mysql -h 127.0.0.1 -P 3306 -u root -p来测试是否安装成功,成功后返回以下信息。并进入MariaDB。

1
2
3
4
5
6
7
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.60-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

  经过以上步骤,使用quit退出MariaDB,就算是成功安装完毕了,我们就能轻松的开始学习了。

数据库基础介绍

  1、所谓数据库服务器,不过是数据库的系统管理程序,这个管理程序可以管理多个数据库,易班来说,我们每开发一个项目就会单独建立一个数据库来进行数据的存储。同时为了保存应用中实体的数据,一般会在数据库中建立多个表,以保存实体的数据。也就是说在MySQL中我们可以建立多个数据库,而每个数据库中又可以建立多个表,分类进行数据管理。
  2、MySQL数据库可以在多个平台上运行,
当今几乎所有的操作系统都可以运行MySQL,并且在哥哥系统上MySQL可以做到很好的物理体系结构的一致性。
  3、在MySQL语法中其实包含着多个数据操作语言,他们各自有各自独立的语法,一起合作构成了MySQL这个强大的体系。

  DDL:数据定义语言,用来维护存储数据的结构。

  DML:L数据操纵语言,用来对数据进行操作。

  DCL:数据控制语言,主要负责权限管理和事务。

  4、存储引擎是数据库管理系统如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。MySQL的核心就是插件式存储引擎,支持多种存储引擎,每种存储引擎都各有各的优缺点,在早期学习数据库过程中仅做了解。

【Linux】Linux编程-gcc,gdb,makefile篇

发表于 2018-12-14 | 分类于 Linux
字数统计: 2.4k

【Linux】Linux编程-gcc,gdb,makefile篇

  在介绍了vim的使用后我们已经可以在Linux中的vim书写代码了,但是如果想要vim使用更加舒畅我们还需要向vim的配置文件中增加相关配置才行,这些都交给大家自行选择了。但是真正想要在Linux中运行代码光靠vim是不够的,我们还需要编译器,调试器,以及文件管理工具共同配合才行。

gcc

  在Linux中我们有现成的编译器可供我们使用,这个编译器就是gcc,gcc的编译标准是C标准委员会所提供的最为官方的编译准则,因此学会使用gcc的使用后我们不光可以在Linux中使用gcc编程也可以在Windows下使用记事本+gcc的方式进行编程。

基本指令

  我们可以使用man gcc打开gcc的说明文档,可以发现,gcc的使用还是比较复杂的,里面有各种指令选项可供我们选择,但是大多的选项我们目前还使用不到,最为常用的语法大家只需要记住一条即可。

  gcc 文件名.c -g -o 编译结果文件名

  -o选项是改名选项,我们在后面跟上自己目标想要使用的名字即可。如果不适用-o选项会自动将结果命名为a.out。-g则是生成Debug版本方便我们后面用gdb进行调试。

编译过程

  在之前我们在Windows下使用ide进行编程的时候,我们总是一个编译运行,ide就自动执行整个编译过程并且执行显示结果了,我们往往无需关心编译过程中编译器都做了哪些事情,但是在Linux下编程的时候往往就更能感受到编译器在编译过程中都做了哪些事,这个过程对我们来说也显得更为重要。

  1、宏替换

  在这个过程下,编译器会将我们在.c文件中以#开头的宏定义和引入头文件预先编译一遍并且加到代码中,这也是以#开头的语句我们叫他预处理语句的原因。我们可以对一个.c文件执行以下命令。

  gcc -E 文件名.c -o 结果文件名.i

  执行过后就会发现当前目录下产生了预处理过后的.i文件,用vim打开文件会发现代码与我们之前所书写的略有不同,因为所有的预处理指令都不见了,因为他们已经和我们所写的代码结合起来了。在这条指令中-E是预处理选项。

  2、编译

  编译过程是将我们之前生成的预处理结果.i文件转换为汇编语言文件的过程,这是一个将高级语言向底层转换的过程。

  gcc -S 文件名.i -o 结果文件名.s

  执行过语句过后目录下便会出现编译过后的.s汇编语言文件,用vim打开后会发现我们的代码已经全部转换为汇编语言的版本了。-S是汇编选项。

  3、汇编

  汇编过程是将我们之前生成的汇编文件转换为机器码的过程,因为我们的计算机只认得机器码,也就是二进制文件,因此我们还需做一步转换才能让计算机识别。

  gcc -c 文件名.s -o 结果文件名.o

  要注意这条语句中的-c是小写的,之前的命令中-S和-E都是大写。在这条语句执行过后我们就会发现目录下多出了-o二进制文件,也就是我们所说的目标文件,同样用vim打开这个文件,会发现全是乱码,因为我们的文件已经转换为二进制文件了。-c是汇编选项。

  4、链接

  在转换为目标文件后我们的编译过程就只需要最后一步了。在C语言程序中我们往往会写很多个C语言文件,最后一步则就是将我们所写的多个文件进行整合汇总的过程。

  gcc 文件名.o -o 结果可执行文件名

  在链接指令中我们无需其他选项,执行完成后即可发现我们的目录中多出了可执行文件,到此编译全部结束。

gdb

  在Linux上的调试器上手有一定的门槛,因为gdb是作为控制台的编译器的是没有图形界面的,这就对我们的操作和调试带来了一定的难度。

基本指令

  最好在编译时加上-g选项生成Debug版本,否则会导致gdb部分指令无法使用。

  1、gdb 可执行文件进入gdb调试。

  2、break + 行号/函数名打断点。

  3、info break查看断点信息。

  4、delte + 断点编号删除断点。

  5、run让程序从头开始运行,遇到断点停止。

  6、continue让程序继续运行。当程序遇到断点停止想要继续运行时使用continue,使用run会让程序重新开始运行。

  7、print 变量名查看当前状态下变量信息。

  8、list查看当前执行到的语句附近的语句。

  9、bt查看调用栈。

  10、next单步执行下一条语句。

  11、quit退出gdb调试器。

核心转储文件

  当我们在ide中对于内存操作不当读取了不合法的内存后我们的程序会强制终止,并且ide会报错,那么我们在Linux中如果执行了类似于读取了非法的内存之类的内存使用不当的错误后会发生什么呢?

  我们可以尝试写一个文件专门用来读取一个非法的内存,来看看会发生什么。

  将我们写好的错误代码编译之后执行。会发现出现了段错误(吐核)的错误信息。对,这就是Linux下内存出错的表现。段错误是Linux内存出错的错误代称,我们暂时先不研究,那么后面的吐核是什么意思呢?所谓吐核就是生成一个错误报告告诉我们哪出错了,更像是车祸现场。但是我们查看这个目录下也没有新的文件生成啊,那么核吐到哪了呢?

  其实核普遍很大,因此不是想吐就能吐的除了来的,那么我们想要这个核能够吐出来该怎么做呢?

  执行指令ulimit -a,然后再执行ulimit -c unlimited指令,之后再去运行我们有错的代码生成的可执行文件即可看到我们当前目录下多出了一个core文件,这个就是所谓的核,那么我们怎么查看这个核的信息呢?我们的每个核都对应一个可执行文件,因此为了查看到正确的信息我们需要将可执行文件与核配对才能查看因此执行以下命令即可用gdb查看核了。gdb 可执行文件 core文件名。这个核也被称为核心转储文件。用gdb打开后即可发现gdb已经将报错的具体信息核代码的行数都列出来了。

  其实在实际编程中我们有很多情况下无法使用gdb进行调试,比如说遇到几率性bug,因此我们要学会用诸如输出的方式来进行调试,归根到底gdb不过是一个辅助我们调试的工具罢了,千万不要依赖于此调试工具,gdb往往更擅长于解决程序崩溃的问题。

makefile

  makefile工具与前两者有很大区别之处就在于其需要哦我们完全自定义指令所执行的内容,也就是说在使用makefile前我们需要首先自行编写一个Makefile文件。

Makefile配置

  首先我们用vim创建一个Makefile文件。vim Makefile。

  Makefile的配置每一条语句都基本有三个模块组成。

  [目标]:[依赖](可无)

    [操作]

  我们首先在Makefile文件中输入以下操作来讲我们编写好的.c文件编译出来。

1
2
目标文件:.c文件
gcc -g .c文件 -o 目标文件

  随后保存退出vim,在控制台中执行make 目标文件命令,即可发现我们我们自动执行了编译语句,.c文件已经被自动编译并生成了目标文件。

  关于makefile的用法有很多十分多样,其中主要体现在Makefile配置的编写,不过一般情况下我们不会经常去编写Makefile文件,一般都是借助工具进行编写。关于Makefile的其他语法还需进一步学习。

【Linux】Linux编程-vim篇

发表于 2018-12-08 | 分类于 Linux
字数统计: 2k

Linux编程 vim篇

第1节

vim是什么

  vim是一种基于Linux环境下的编辑器,上节也给大家已经提过,我们要想在Linux下编程需要四样东西来替代vs这个集成的开发环境,其中最为核心的载体就是这个vim的编辑器,没有它,我们甚至无法书写代码。

  在Linux下除过vim这个编辑器十分经典之外,还有一款编辑器也深受大家喜爱,就是emacs。这两款编辑器之所以受大家欢迎以至于后续几十年的编辑器都无法超越有几点最主要的原因。

  1、支持丰富的快捷键和编辑方式。

  2、支持非常强大的扩展能力。

  3、都有一门强大的编程语言作为支撑。

  说到支撑他们的编程语言就不得不提emacs背后的编程语言lisp。之前我们说万物都出自C语言,其实并非如此,除过C语言外还有一个编程语言的祖师爷就是lisp。这是两款风格相差很大的编程语言,如果大家有兴趣可以去自行了解。这里不再赘述

  虽说vim和emacs这两款编辑器各有各的千秋,都是编辑器中的祖师爷,但我选择emacs作为我的首选编辑器,也作为接下来讲解的重点(如果有同学对emacs感兴趣自然可以选择emacs作为自己的首选),并且最好的是在每一个Linux系统中都自带了vim编辑器,因此我们无需在自行安装了。

vim使用

vim基础介绍

  由于我们的Linux已经自带了vim,所以我们可以直接使用。输入vim即可看到它的界面了。

vim使用

  会发现vim的界面十分的丑,毕竟是在控制台下,这也是无可奈何啊。那么如何退出界面呢?输入:q即可退出了。注意:前面要有个冒号才行!。

  关于vim使用呢其实vim官方有一篇教学文档,大家退出vim界面后输入vimtutor即可进入官方教学文档。(输入vimt+tab即可补全了,tab是补全键可以帮助我们快速输入指令,要常用)。

  在官方教学文档里大家跟着教学步骤一步一步练习,多多练习即可掌握vim的所有常用操作,一定切记一点:千万不要硬背指令或者快捷键,在使用中记忆才为上策。

  在vim下一共有普通,插入,命令,可视,替换五种模式,在我们用vim打开一篇文档,我们默认进入的就是普通模式,在普通模式下更为方便我们进行文档浏览,在这个模式下我们有丰富的快捷键供我们在页面间进行条件及光标移动。插入模式是当我们想要修改文档时才会进入的模式。命令模式里有一些命令方便我们对vim进行配置修改,页面跳转等等,用法丰富。可视模式下我们最常用的操作是选中文档片段进行删除,复制或剪切。替换模式则允许我们对文本内容进行替换。

vim指令

  vim的快捷键及指令很多,在此我们着重介绍常用的几条指令。同时为了方便快捷键操作vim分为了几个模式,而我们大多数快捷键都是在普通模式下使用的。

  1、在vim中我们除了上下左右方向键可以移动光标外,还提供了新的四个快捷键供我们进行光标移动。j:下;k:上;h:左;l:右。在vim下尤为推荐大家使用这四个控制光标移动的方向键,因为使用起来更为方便,最主要这是你学习过vim的象征!

  2、普通模式进入其他模式的方式有很多种,最为普通的是按i进入插入模式,:进入命令模式,v进入可视模式,R进入替换模式。而其他模式回到普通模式下最为常用的方法永远是按esc。因此常按esc往往也会成为使用vim的程序员的习惯

  3、x进行单个字符的删除。

  4、在一个目录下输入指令vim 文件名打开一个已有的文件或创建一个新文件。输入:进入命令模式下输入w进行保存,q进行退出,wq保存并退出,q!不保存强制退出。

  5、w可将光标从目前位置移动到下一个单词的头部,e可将光标从目前位置移动到下一个单词的尾部,$则可以将光标移动到行末,b表示移动到上个单词头。

  6、d表示删除指令,一般和光标跳转指令连用。dw表示从光标目前位置删除到下一个单词头部位置(不包含头部),de表示从光标目前为止删除到下一个单词的尾部位置(包含尾部),d$表示从光标当前位置删除到行尾(删除整行)。

  7、同时我们可以使用数字加指令的方式对很多指令进行重复执行。例如2w == w+w,d2w==dw + dw,等等,可自行尝试。

  8、普通模式下u表示撤销操作,U表示撤销对整行的操作。Ctrl r表示重做,及撤销撤销操作。

  9、dd将整行进行删除/剪切,p将剪切板里的内容往光标后进行粘贴,P将剪贴板里的内容往光标前进行粘贴。

  10、r将单个字符进行替换,R将进入替换模式接下来输入的内容都替换为输入内容,知道返回普通模式。

  11、c命令于d命令类似,依然于其他命令构成组合命令,例如cw,ce等,但不同是c命令使用完后会进入插入模式。

  12、输入:进入命令模式下,输入set nu可现实行号。

  13、ctrl g显示当前行数,并显示光标位置信息。

  14、shift g跳转至文本尾,gg跳转至文本头,'回到跳转前行数。

  15、/查找内容进入命令模式下,输入查找内容进行查找,并且n寻找下一个,N寻找上一个。

  16、光标在括号上按%进行括号匹配快速移动,自动跳转至匹配括号处。

  17、输入:进入命令模式之后输入s/查找内容/替换结果/gc进行一行的内容替换,%s/查找内容/替换结果/gc进行全文的内容替换,其中g表示搜索整个一行,c表示替换前询问%s表示全文替换。

  18、o在本行下创建新行并进入插入模式,O在本行上面创建新行并进入插入模式。

  19、v进入可视模式,光标移动选中语句后按y可进行复制。同时yy可复制一行。同样的使用p或P进行粘贴。

  20、a进入插入模式表示在光标后插入,与i类似。同时使用I或A在行首或行尾插入。

  vim的基础指令介绍就到此为止了,vim的使用远不止这些快捷键及指令,想要更好的更熟练的使用vim还需要不断地练习,在实践中学习才是最高效的。

1…6789
MisakiFx

MisakiFx

Hard working or giving up!!!

86 日志
10 分类
64 标签
GitHub E-Mail 网易云音乐 CSDN

© 2020 MisakiFx
我的网站的访客数:
|
主题 — NexT.Pisces v5.1.4
博客全站共273.4k字