C语言深入理解指针(4)--指针笔试题解析

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

C语言深入理解指针(4)

收录于专栏【C语言学习
本专栏旨在分享学习C语言学习的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1. sizeof和strlen的对⽐

1.1 sizeof

1.2 strlen

1.3 sizeof 和strlen的对⽐

2. 数组和指针笔试题解析

2.1 ⼀维数组

2.2 字符数组

示例一:

示例二:

示例三:

示例四:

示例五:

实例六:

2.3 ⼆维数组

3. 指针运算笔试题解析

3.1 题⽬1:

3.2 题⽬2

3.3 题⽬3

3.4 题⽬4

3.5 题⽬5

3.6 题⽬6

3.7 题⽬7


1. sizeof和strlen的对⽐

1.1 sizeof

在学习操作符的时候,我们学习了 sizeofsizeof 计算变量所占内存内存空间⼤⼩的,单位是 字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。

sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

比如:

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof a);
	printf("%d\n", sizeof(int));
	return 0;
}

输出结果: 

1.2 strlen

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:

size_t strlen ( const char * str );

 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。 strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。

比如:


#include <stdio.h>
int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));

	printf("%d\n", sizeof(arr1));
	printf("%d\n", sizeof(arr1));
	return 0;
}

输出结果:

注意35是随机的,当没有\0时,strlen 会⼀直向后找 \0 字符,直到找到为⽌,所以是不确定的

1.3 sizeof 和strlen的对⽐

sizeofstrlen

1. sizeof是操作符

2. sizeof计算操作数所占内存的⼤⼩, 单位是字节

3. 不关注内存中存放什么数据

1. strlen是库函数,使⽤需要包含头⽂件 string.h

2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的隔个数

3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可 能会越界

2. 数组和指针笔试题解析

2.1 ⼀维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
}

输出结果:

解析如下:di

第一个:sizeof加数组名,在上一节说过,代表求整个数组的内存大小,一个整数为四个字节,四个整数即为16个字节

第二个:a+0代表的是首元素的地址,指针的大小只与平台有关,我这里是X64所以是8

第三个:对首元素的指针进行解引用,即求1的内存,所以为4个字节

第四个:代表的是首元素后面一个元素的地址大小,所以为8

第五个:求第二个元素的内存,所以为4

第六个:返回的是整个数组的地址,地址的内存只与平台有关,所以为8

第七个:对整个数组进行解引用,所以为16

第八个:求得是数组地址下一个地址,是地址内存就是固定得,为8

第九个:求得是地址,固定为8

第十个:也是一样,求的是地址,为8

2.2 字符数组

示例一:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
}

输出结果:

解析如下:

第一个:sizeof加数组名,在上一节说过,代表求整个数组的内存大小,一个字符为1个字节,6个字符即为6个字节

第二个:a+0代表的是首元素的地址,指针的大小只与平台有关,我这里是X64所以是8

第三个:对首元素的指针进行解引用,即求1的内存,所以为1个字节

第四个:求第二个元素的内存,所以为1

第五个:返回的是整个数组的地址,地址的内存只与平台有关,所以为8

第六个:求得是数组地址下一个地址,是地址内存就是固定得,为8

第七个:求得是数组地址下一个地址,是地址内存就是固定得,为8

示例二:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
}

  strlen 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。 strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。

所以我们使用 strlen 时一定要注意这个问题!

示例三:

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
}

输出结果:

 调式结果:

注意:这样得初始化会自动补上"\0" 

 解析如下:

第一个:sizeof加数组名,在上一节说过,代表求整个数组的内存大小,一个字符为1个字节,7个字符即为7个字节(注意"\0"也算)

第二个:a+0代表的是首元素的地址,指针的大小只与平台有关,我这里是X64所以是8

第三个:对首元素的指针进行解引用,即求1的内存,所以为1个字节

第四个:求第二个元素的内存,所以为1

第五个:返回的是整个数组的地址,地址的内存只与平台有关,所以为8

第六个:求得是数组地址下一个地址,是地址内存就是固定得,为8

第七个:求得是数组地址下一个地址,是地址内存就是固定得,为8

示例四:

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
}

输出结果:

解析如下:

第一个:strlen加数组名,数组名就是首元素得地址,所以strlen会一直找,直到找到\0,所以为6

第二个:strlen接收得也是首元素得地址,所以也为6

第三个和第四个:传入得不是地址,而是一个值,所以打印不出结果

第五个:返回的是整个数组的地址,返会得其实也是地址首元素得地址,所以也为6

第六个:求得是数组地址下一个地址,内容不确定,随机

第七个:求得是数组地址首元素得下一个地址,所以为5

示例五:

int main()
{
	char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));
}

输出结果:

解析如下:

第一个:sizeof求得时指针p得大小,所以为8

第二个:求得是字符指针p下一个地址,是地址内存就是固定得,为8

第三个:对字符指针p进行解引用,即求a的内存,所以为1个字节

第四个:求第一个元素的内存,所以为1

第五个:对指针p进行取地址,地址内存就是固定得,为8

第六个:求得是对指针p进行取地址得下一个地址,是地址内存就是固定得,为8

第七个:求得是字符数组首元素地址下一个地址,是地址内存就是固定得,为8

实例六:

int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));
}

输出结果:

 

第一个:strlen加字符数组指针名,字符数组指针名就是首元素得地址,所以strlen会一直找,直到找到\0,所以为6

第二个:strlen接收得也是首元素下一个地址,所以为5

第三个和第四个:传入得不是地址,而是一个值,所以打印不出结果

第五个:传入p得地址,strlen会一直找,直到找到\0,所以为6

第六个:传入p地址的下一个,随机不确定

第七个:传入p首元素的下一个地址,所以为5

2.3 ⼆维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));
}

输出结果


 

这里的分析和一维数组一样,我这里只说第三个和第二个:

    printf("%d\n", sizeof(a[0])); 

sizeof加数组名求得是整个数组得内存,而这里求得是第一行所有元素得内存,即为16
    printf("%d\n", sizeof(a[0] + 1));

这里应该a[0] + 1先算所以,他这里返回得是地址,答案为8

数组名的意义:

1. sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。

2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。

3. 除此之外所有的数组名都表⽰⾸元素的地址。

3. 指针运算笔试题解析

3.1 题⽬1:

#include <stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}
//程序的结果是什么?

程序的结果是什么?

这里得ptr指向的是整个数组地址的下一个,应为&a取得是整个数组的地址

所以它打印的结果应该为2,5

*(a + 1):数组名代表首元素的地址,再加一,即取得是第二个元素得地址,在进行解引用:

*(ptr - 1) :

输出结果:

3.2 题⽬2

 在X86环境下

假设结构体的⼤⼩是20个字节

程序输出的结构是啥?

struct Test
{

	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}
  • p + 0x1:这是一个指针运算,它将 p 指针向后移动了一个结构体的大小(20 字节),这里注意: 0x100014 是16进制。因此,它将指向 0x100014 地址。
  • (unsigned long)p + 0x1:这是一个类型转换,将指针 p 转换为 unsigned long 类型,然后再加上 0x1。这将得到 0x100001
  • (unsigned int*)p + 0x1:这也是一个类型转换,将指针 p 转换为 unsigned int* 类型,然后再加上 0x1。这将得到 0x100004

输出结果:

3.3 题⽬3

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}
  1. 首先,我们定义了一个二维整数数组 a,其中包含了三个子数组,每个子数组有两个整数元素。注意,这里的初始化方式有点特殊,使用了逗号运算符,但实际上只有最后一个值被赋给了数组元素。因此,a 的内容实际上是 {1, 3, 5}
  2. 然后,我们创建了一个指向整数的指针 p,并将其指向了 a[0],即第一个子数组的首地址。
  3. 接下来,我们打印了 p[0] 的值。由于 p 指向了 a[0],所以 p[0] 实际上是 a[0][0],即第一个子数组的第一个元素,也就是 1

输出结果:

3.4 题⽬4

 //假设环境是x86环境,程序输出的结果是啥?

#include <stdio.h>
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

解析:

这里注意a和p的类型是不一样的

a的类型为int(*)[5]

p的类型为int(*)[4]

当返回%d时,就是返回了两个地址相差的距离,也是就-4

而用地址打印时,就是返回-4的补码,用16进制表示 

3.5 题⽬5
 

#include <stdio.h>
 int main()
 {
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
 }
  1. 首先,我们定义了一个字符型指针数组 a[],其中包含三个字符串元素:“work”、“at” 和 “alibaba”。
  2. 然后,我们定义了一个指向字符型指针的指针 pa,并将其初始化为 a 数组的首元素地址,即 pa = a;
  3. 接下来,我们执行了 pa++ 操作,将 pa 指向了数组 a 的第二个元素,即指向了字符串 “at” 的首元素地址。
  4. 最后,我们使用 printf("%s\n", *pa); 打印了 *pa 所指向的字符串,即输出了 “at”。

输出结果

3.6 题⽬6

#include <stdio.h>
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}
  1. 首先,我们定义了一个二维整型数组 aa[2][5],并初始化了其中的元素。
  2. 然后,我们定义了两个指针 ptr1 和 ptr2
    • ptr1 的值被设置为 (&aa + 1),即指向了 aa 数组之后的内存位置。
    • ptr2 的值被设置为 *(aa + 1),即指向了 aa 数组的第二行的首元素地址。
  3. 接下来,我们使用 printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); 打印了两个值:
    • *(ptr1 - 1) 表示 ptr1 指向的内存位置之前的整型值。
    • *(ptr2 - 1) 表示 ptr2 指向的地址之前的整型值。

现在让我们具体分析一下:

  • ptr1 指向了 aa 数组之后的内存位置,因此 *(ptr1 - 1) 实际上是访问了 aa 数组之后的内存位置之前的整型值。由于 aa 数组是一个二维数组,其内存布局是连续的,所以这个值应该是 10
  • ptr2 指向了 aa 数组的第二行的首元素地址,即指向了整型值 6 的地址。因此,*(ptr2 - 1) 实际上是访问了 6 之前的整型值,即 5

因此,程序的输出结果应该是 10,5

 

3.7 题⽬7

#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

 

这里注意CPP指针,只有在++,--时发生了改变,CPP-2并未对指针发生改变 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/615987.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[嵌入式系统-75]:RT-Thread-快速上手:正点原子探索者 STM32F407示例

目录 正点原子探索者 STM32F407 上手指南 1. 简介 2. 准备工作 3. 运行第一个示例程序 3.1 编译下载 3.2 运行 继续学习 正点原子探索者 STM32F407 上手指南 1. 简介 探索者 STM32F407 是正点原子推出的一款基于 ARM Cortex-M4 内核的开发板&#xff0c;最高主频为 16…

启用dell服务器的iDRAC

插网线 观察到 dell服务器背板左侧有一个网口&#xff0c;标有iDRAC字样&#xff0c;使用网线将该网口和网段所在的交换机连接起来。 网络配置 重启计算机&#xff0c;依照屏幕显示按F2进入SystemSetup。选择iDRACsettings – Network&#xff0c;需要改动的如下&#xff08;现…

WPS表格:使用vlookup函数解决乱序数据对应问题

我们常常会遇到两个表格的内容相同&#xff0c;但是顺序不一致的情况。并且这种顺序无关于简单的排序&#xff0c;而是一种业务性很强的复杂排序规则。下面我举个例子&#xff0c;使用VLOOKUP复制数据。 假设太阳系行星举办了一次卖萌比赛&#xff0c;由太阳妈妈决定谁是最萌的…

Java | Leetcode Java题解之第84题柱状图中最大的矩形

题目&#xff1a; 题解&#xff1a; class Solution {public int largestRectangleArea(int[] heights) {int n heights.length;int[] left new int[n];int[] right new int[n];Arrays.fill(right, n);Deque<Integer> mono_stack new ArrayDeque<Integer>();f…

【git】通过JetBrains IDE对git的操作

应该适用于所有jetbrains产品。 一、拉取(pull)代码 上方工具栏-Git-克隆。然后填写git地址与本地存放地址。 二、搁置 修改代码后搁置代码&#xff08;不提交&#xff0c;但是也不撤销已修改的代码&#xff0c;把它暂存起来&#xff09;。 界面的左上角。1->2->3。…

[算法][差分数组][leetcode]1094. 拼车

地址&#xff1a; https://leetcode.cn/problems/car-pooling/description/ 解法一&#xff1a;暴力解法 class Solution {public boolean carPooling(int[][] trips, int capacity) {//特殊条件判断if(nulltrips||capacity<0){return false;}int [] d new int[1001];//暴…

Web自动化-日志收集

目标 1. 理解日志的相关概念 2. 掌握日志的基本用法 3. 掌握日志的高级用法 一、日志相关概念 目标 1. 了解日志的概念 2. 理解日志的作用 3. 掌握常见的日志级别 1. 日志 概念&#xff1a;日志就是用于记录系统运行时的信息&#xff0c;对一个事件的记录&#xff1b…

Qt---信号和槽

一、信号和槽机制 所谓信号槽&#xff0c;实际就是观察者模式。当某个事件发生之后&#xff0c;比如&#xff0c;按钮检测到自己被点击了一下&#xff0c;它就会发出一个信号&#xff08;signal&#xff09;。这种发出是没有目的的&#xff0c;类似广播。如果有对象对这个信号…

SpringBoot自定义初始化sql文件 支持多类型数据库

我在resources目录下有init.sql初始化sql语句 指定sql文件的地址 sql内容如下&#xff1a; /*角色表*/ INSERT INTO #{schema}ccc_base_role (id, create_time, create_user_id, is_delete, role_name, status, update_time, update_user_id) VALUES(b89e30d81acb88448d412…

2024数据分析管理、数字经济与教育国际学术会议(ICDAMDEE2024)

2024数据分析管理、数字经济与教育国际学术会议(ICDAMDEE2024) 会议简介 2024年数据分析管理、数字经济和教育国际学术会议&#xff08;ICDAMDEE 2024&#xff09;将在武汉举行。会议不仅展示了来自世界各地的研究专家围绕数据分析管理、数字经济和教育的最新科研成果&#xf…

C语言 | Leetcode C语言题解之第84题柱状图中最大的矩形

题目&#xff1a; 题解&#xff1a; int largestRectangleArea(int* heights, int heightsSize) {int st[heightsSize];int p[2];p[0]-1,p[1]heightsSize;int size0,result0;st[size]0;for(int i1;i<heightsSize;i){ while(size!0&&heights[i]<heights[st[size-1…

Elasticsearch入门基础和集群部署

Elasticsearch入门基础和集群部署 简介基础概念索引&#xff08;Index&#xff09;类型&#xff08;Type&#xff09;&#xff08;逐步弃用&#xff09;文档&#xff08;Document&#xff09;字段&#xff08;Field&#xff09;映射&#xff08;Mapping&#xff09;分片&#x…

从零自制docker-12-【overlayfs】

文章目录 overlayfsexec.Command("tar", "-xvf", busyboxTarURL, "-C", busyboxURL).CombinedOutput()exec.Command格式差异 挂载mount卸载unmount代码地址结果演示 overlayfs 就是联合文件系统&#xff0c;将多个文件联合在一起成为一个统一的…

HTTP1.1的优化措施

3.2 HTTP/1.1 如何优化&#xff1f; 可以从以下三个方面来优化http/1.1协议&#xff1a; 尽量避免发送 HTTP 请求&#xff1b; 在需要发送 HTTP 请求时&#xff0c;考虑如何减少请求次数&#xff1b; 减少服务器的 HTTP 响应的数据大小&#xff1b; 避免发送HTTP请求 对于…

【计算机网络】数据链路层的功能

数据链路层的基本功能&#xff1a; 封装成帧透明传输差错检测 数据链路层使用的信道主要有两种 点对点信道——PPP协议广播信道——CSMA/CD协议(有线局域网)、CSMA/CA协议(无线局域网) 数据链路层所处的地位 从图中可以看出&#xff0c;数据从主机H1送到主机H2需要在路径中…

苍穹外卖项目---------收获以及改进(9-12)

①Spring Task-------实现系统定时任务 概念&#xff1a; 应用场景&#xff1a; 使用步骤&#xff1a; 实现订单超时和前一天派送中的订单的自动任务处理&#xff1a; Component Slf4j public class Mytask {Autowiredprivate OrderServiceimpl orderServiceimpl;/*** 处理订…

05-11 周六 一键完成FastBuild镜像部署功能的开发

05-11 周六 一键完成FastBuild镜像部署功能的开发 时间版本修改人描述2024年5月11日21:50:15V0.1宋全恒新建文档 简介 注&#xff0c;需要提前完成从DockerService到FastBuild主机的免密&#xff0c;因为脚本使用了ssh命令。 博客描述04-22 周日 阿里云-瑶光上部署FastBuild过…

最大子序列的分数

题目链接 最大子序列的分数 题目描述 注意点 n nums1.length nums2.length从nums1和nums2中选一个长度为k的子序列对应的下标对nums1中下标对应元素求和&#xff0c;乘以nums2中下标对应元素的最小值得到子序列的分数0 < nums1[i], nums2[j] < 1000001 < k < …

MVCC 详解

介绍 MVCC&#xff0c;全称 Multi-Version Concurrency Control&#xff0c;即多版本并发控制 MVCC的目的主要是为了提高数据库并发性能&#xff0c;用更好的方式去处理读-写冲突&#xff0c;做到即使有读写冲突时&#xff0c;也能做到不加锁。 这里的多版本指的是数据库中同时…

Django项目运行报错:ModuleNotFoundError: No module named ‘MySQLdb‘

解决方法&#xff1a; 在__init__.py文件下&#xff0c;新增下面这段代码 import pymysql pymysql.install_as_MySQLdb() 注意&#xff1a;确保你的 python 有下载 pymysql 库&#xff0c;没有的话可以使用 pip install pymysql安装 原理&#xff1a;用pymysql来代替mysqlL…