当前位置: 首页 > news >正文

数组和指针典型例题合集(一维数组、字符数组、二维数组)

1.一维数组

数组名的理解

数组名是数组首元素(第一个元素)的地址

但是有两个例外:

1.sizeof (数组名)—— 数组名表示整个数组,就算的是整个数组的大小,单位是字节。

2.&数组名 —— 数组名表示整个数组,取出的是整个数组的地址。

除此之外,所有的数组名表示的都是数组首元素(第一个元素)的地址。

对于数组名a

a  ——  数组名

a  ——  首元素(第一个元素)的地址,int*,  a+1 跳过一个元素

&a  ——  数组的地址,int(*)[4], &a+1 跳过y

辨析一下:

int a[] = { 1,2,3,4 };
// 数组有4个元素


printf("%zd\n", sizeof(a));
// 16 
// sizeof(数组名)


printf("%zd\n", sizeof(a + 0));
// 4/8 
// a是首元素的地址——类型是int*,a+0,还是首元素地址

printf("%zd\n", sizeof(*a));
// 4 
// a是首元素的地址,*a就是首元素,大小是4个字节
// *a == a[0] == *(a+0)

printf("%zd\n", sizeof(a + 1));
// 4/8 
// a是首元素的地址——类型是int*,a+1跳过一个整型,a+1就是第二个元素的地址

printf("%zd\n", sizeof(a[1]));
// 4
// a[1]就是第二个元素,大小4个字节

printf("%zd\n", sizeof(&a));
// 4/8
// &a —— 是数组的地址,数组的地址也是地址
// 而sizeof(&a)取出的是整个数组的地址,是地址就是 4/8 个字节

printf("%zd\n", sizeof(*&a));
// 16
// * 和 & 相互抵消了
// &a 是数组的地址,类型是int(*)[4]
// 对数组指针解引用访问的是数组,计算的是数组的大小

printf("%zd\n", sizeof(&a + 1));
// 4/8
// &a+1 跳过一个数组,数组的地址还是地址,地址就是 4/8 个字节

printf("%zd\n", sizeof(&a[0]));
// 4/8
// 首元素的地址

printf("%zd\n", sizeof(&a[0] + 1));
// 4/8    
// 数组第二个元素的地址    

运行代码验证一下:

//一维数组
int main()
{int a[] = { 1,2,3,4 };// 数组有4个元素printf("%zd\n", sizeof(a));// 16 // sizeof(数组名)printf("%zd\n", sizeof(a + 0));// 4/8 // a是首元素的地址——类型是int*,a+0,还是首元素地址printf("%zd\n", sizeof(*a));// 4 // a是首元素的地址,*a就是首元素,大小是4个字节// *a == a[0] == *(a+0)printf("%zd\n", sizeof(a + 1));// 4/8 // a是首元素的地址——类型是int*,a+1跳过一个整型,a+1就是第二个元素的地址printf("%zd\n", sizeof(a[1]));// 4// a[1]就是第二个元素,大小4个字节printf("%zd\n", sizeof(&a));// 4/8// &a —— 是数组的地址,数组的地址也是地址// 而sizeof(&a)取出的是整个数组的地址,是地址就是 4/8 个字节printf("%zd\n", sizeof(*&a));// 16// * 和 & 相互抵消了// &a 是数组的地址,类型是int(*)[4]// 对数组指针解引用访问的是数组,计算的是数组的大小printf("%zd\n", sizeof(&a + 1));// 4/8// &a+1 跳过一个数组,数组的地址还是地址,地址就是 4/8 个字节printf("%zd\n", sizeof(&a[0]));// 4/8// 首元素的地址printf("%zd\n", sizeof(&a[0] + 1));// 4/8	// 数组第二个元素的地址																																																			return 0;
}

运行结果: 

(注意:4/8是根据代码运行环境而定的,本次运行代码是在x64环境下的)

2.字符数组

对以下代码段进行辨析:

代码1:

char arr[] = { 'a','b','c','d','e','f' };
//一个字符一个字节

printf("%d\n", sizeof(arr));
// 6 —— char
// 数组名单独放在sizeof内部了,计算的是数组大小,单位是字节

printf("%d\n", sizeof(arr + 0));
// 4/8
// arr是数组名,表示首元素的地址,arr+0还是首元素的地址
// 是地址就是4/8

printf("%d\n", sizeof(*arr));
// 1
// arr是首元素的地址,*arr就是首元素,大小是1个字节
// *arr —— arr[0] —— *(arr+0)

printf("%d\n", sizeof(arr[1]));
// 1
// arr[1]是第二个元素,大小是1个字节

printf("%d\n", sizeof(&arr));
// 4/8
// &arr 是数组地址,数组地址也是地址,是地址就是4/8
// &arr —— char(*)[6]

printf("%d\n", sizeof(&arr + 1));
// 4/8
// 跳过整个数组,指向了这个数组后面的空间地址

printf("%d\n", sizeof(&arr[0] + 1));
// 4/8
// 是第二个元素的地址,是地址就是4/8

代码运行验证一下(x64):

int main()
{char arr[] = { 'a','b','c','d','e','f' };//一个字符一个字节printf("%d\n", sizeof(arr));// 6 —— char// 数组名单独放在sizeof内部了,计算的是数组大小,单位是字节printf("%d\n", sizeof(arr + 0));// 4/8// arr是数组名,表示首元素的地址,arr+0还是首元素的地址// 是地址就是4/8printf("%d\n", sizeof(*arr));// 1// arr是首元素的地址,*arr就是首元素,大小是1个字节// *arr —— arr[0] —— *(arr+0)printf("%d\n", sizeof(arr[1]));// 1// arr[1]是第二个元素,大小是1个字节printf("%d\n", sizeof(&arr));// 4/8// &arr 是数组地址,数组地址也是地址,是地址就是4/8// &arr —— char(*)[6]printf("%d\n", sizeof(&arr + 1));// 4/8// 跳过整个数组,指向了这个数组后面的空间地址printf("%d\n", sizeof(&arr[0] + 1));// 4/8// 是第二个元素的地址,是地址就是4/8return 0;
}

运行结果:

代码2:

换为 strlen(#include<string.h>)

char arr[] = { 'a','b','c','d','e','f' };
//arr 中为 a b c d e f

printf("%d\n", strlen(arr));

// 随机的
// arr是首元素地址
// 数组中没有 \0 导致越界访问,结果是随机的

printf("%d\n", strlen(arr + 0));
// 随机的
// arr + 0 是首元素地址
// 数组中没有 \0 导致越界访问,结果是随机的

//printf("%d\n", strlen(*arr));
// err
// arr是首元素地址
// *arr是首元素,就是 a
// a的ASCII码值为97
// 而 strlen 会将97当成地址,但是97不是我们的地址
// 在内存中,有一大部分空间是不允许被占用的
// 程序会崩溃,得到的是野指针,代码有问题

//printf("%d\n", strlen(arr[1]));
// err
// 同理,arr[1] 是 b

printf("%d\n", strlen(&arr));
// 随机值
// &arr 是数组的地址,起始位置是第一个元素

printf("%d\n", strlen(&arr + 1));
// 随机值
// &arr + 1 是跳过了这个数组的地址

printf("%d\n", strlen(&arr[0] + 1));
// 随机值
// 从第二个元素开始向后统计的

代码验证一下:

//换为strlen
int main()
{char arr[] = { 'a','b','c','d','e','f' };//arr 中为 a b c d e fprintf("%d\n", strlen(arr));// 随机的// arr是首元素地址// 数组中没有 \0 导致越界访问,结果是随机的printf("%d\n", strlen(arr + 0));// 随机的// arr + 0 是首元素地址// 数组中没有 \0 导致越界访问,结果是随机的//printf("%d\n", strlen(*arr));// err// arr是首元素地址// *arr是首元素,就是 a// a的ASCII码值为97// 而 strlen 会将97当成地址,但是97不是我们的地址// 在内存中,有一大部分空间是不允许被占用的// 程序会崩溃,得到的是野指针,代码有问题//printf("%d\n", strlen(arr[1]));// err// 同理,arr[1] 是 bprintf("%d\n", strlen(&arr));// 随机值// &arr 是数组的地址,起始位置是第一个元素printf("%d\n", strlen(&arr + 1));// 随机值// &arr + 1 是跳过了这个数组的地址printf("%d\n", strlen(&arr[0] + 1));// 随机值// 从第二个元素开始向后统计的return 0;
}

运行结果:

代码3:

printf("%d\n", sizeof(arr));
// 7
// arr是数组名,单独放在sizeof内部
// 计算的是数组总大小,是7个字节

printf("%d\n", sizeof(arr + 0));
// 4/8
// arr表示数组首元素的地址,arr+0还是首元素的地址
// 是地址就是4/8

printf("%d\n", sizeof(*arr));
// 1
// arr表示数组首元素的地址
// *arr是首元素,大小是1给个字节

printf("%d\n", sizeof(arr[1]));
// 1
// 是第二个元素,大小1字节

printf("%d\n", sizeof(&arr));
// 4/8
// &arr是数组地址
// 是地址就是4/8

printf("%d\n", sizeof(&arr + 1));
// 4/8
// &arr是数组地址,+1是指向跳过整个数组的地址
// 是地址就是4/8

printf("%d\n", sizeof(&arr[0] + 1));
// 4/8
// &arr[0] + 1是第二个元素的地址
// 是地址就是4/8

代码运行验证一下:

int main()
{char arr[] = "abcdef";printf("%d\n", sizeof(arr));// 7// arr是数组名,单独放在sizeof内部// 计算的是数组总大小,是7个字节printf("%d\n", sizeof(arr + 0));// 4/8// arr表示数组首元素的地址,arr+0还是首元素的地址// 是地址就是4/8printf("%d\n", sizeof(*arr));// 1// arr表示数组首元素的地址// *arr是首元素,大小是1给个字节printf("%d\n", sizeof(arr[1]));// 1// 是第二个元素,大小1字节printf("%d\n", sizeof(&arr));// 4/8// &arr是数组地址// 是地址就是4/8printf("%d\n", sizeof(&arr + 1));// 4/8// &arr是数组地址,+1是指向跳过整个数组的地址// 是地址就是4/8printf("%d\n", sizeof(&arr[0] + 1));// 4/8// &arr[0] + 1是第二个元素的地址// 是地址就是4/8return 0;
}

运行结果(x64):

代码4:

char arr[] = "abcdef";
//      a b c d e f \0

printf("%d\n", strlen(arr));
// 6

printf("%d\n", strlen(arr + 0));
// 6
// arr + 0 表示从 arr 所指向的位置偏移 0 个元素
// 等同于arr,还是指向 arr 数组的首元素
// 向后数到 \0 前,有6个字符

printf("%d\n", strlen(*arr));
// err
// 传给 stlen 的是第一个元素 a 的ASCII - 97
// 越界访问

printf("%d\n", strlen(arr[1]));
// err
// 传给 stlen 的是第二个元素 b 的ASCII - 97
// 越界访问

printf("%d\n", strlen(&arr));
// 6
// 是数组的地址,也是首元素的地址
// 从数组的首元素出发的直到 \0 前

printf("%d\n", strlen(&arr + 1));
// 随机值
// 是跳过整个数组的下一个地址
// 在下一段中,不知道什么时候到 \0

printf("%d\n", strlen(&arr[0] + 1));
// 5
// 是数组第二个元素的地址

代码验证一下:

int main()
{char arr[] = "abcdef";//      a b c d e f \0printf("%d\n", strlen(arr));// 6printf("%d\n", strlen(arr + 0));// 6// arr + 0 表示从 arr 所指向的位置偏移 0 个元素// 等同于arr,还是指向 arr 数组的首元素// 向后数到 \0 前,有6个字符//printf("%d\n", strlen(*arr));// err// 传给 stlen 的是第一个元素 a 的ASCII - 97// 越界访问//printf("%d\n", strlen(arr[1]));// err// 传给 stlen 的是第二个元素 b 的ASCII - 97// 越界访问printf("%d\n", strlen(&arr));// 6// 是数组的地址,也是首元素的地址// 从数组的首元素出发的直到 \0 前printf("%d\n", strlen(&arr + 1));// 随机值// 是跳过整个数组的下一个地址// 在下一段中,不知道什么时候到 \0printf("%d\n", strlen(&arr[0] + 1));// 5// 是数组第二个元素的地址return 0;
}

运行结果(x64):

代码5:

const char* p = "abcdef";
// "abcdef"是常量字符串
// a b c d e f \0
// p指向a
// p是指针变量

printf("%d\n", sizeof(p));
// 4/8
// 计算指针变量的大小,和地址大小一样
// 是地址就是4/8

printf("%d\n", sizeof(p + 1));
// 4/8
// p + 1是 b 的地址

printf("%d\n", sizeof(*p));
// 1
// p 的类型是 char*,*p 则是 char 类型
// char 类型是1个字节

printf("%d\n", sizeof(p[0]));
// 1
// p[0] —— *(p+0) —— *p —— ‘a’
// 把常量字符串想象为数组
// p 可以理解为数组名
// p[0] 就是首元素
// p 的类型是 char*,*p 则是 char 类型
// char 类型是1个字节

printf("%d\n", sizeof(&p));
// 4/8
// &p 是对 p 取地址
// 地址大小是4/8字节

printf("%d\n", sizeof(&p + 1));
// 4/8
// p 的类型是 char*
// p 为一级指针,对一级指针取地址要用二级指针接收
// char* q = &p;
// &p + 1 == q + 1
// &p + 1 就是跳过 p 指针变量后的地址

printf("%d\n", sizeof(&p[0] + 1));
// 4/8
// p[0] 就是首元素
// &p[0] 是首元素地址,&p[0] + 1是第二个元素地址
// 地址大小是4/8字节

代码验证一下:

int main()
{const char* p = "abcdef";// "abcdef"是常量字符串// a b c d e f \0// p指向a// p是指针变量printf("%d\n", sizeof(p));// 4/8// 计算指针变量的大小,和地址大小一样// 是地址就是4/8printf("%d\n", sizeof(p + 1));// 4/8// p + 1是 b 的地址printf("%d\n", sizeof(*p));// 1// p 的类型是 char*,*p 则是 char 类型// char 类型是1个字节printf("%d\n", sizeof(p[0]));// 1// p[0] —— *(p+0) —— *p —— ‘a’// 把常量字符串想象为数组// p 可以理解为数组名// p[0] 就是首元素// p 的类型是 char*,*p 则是 char 类型// char 类型是1个字节printf("%d\n", sizeof(&p));// 4/8// &p 是对 p 取地址// 地址大小是4/8字节printf("%d\n", sizeof(&p + 1));// 4/8// p 的类型是 char*// p 为一级指针,对一级指针取地址要用二级指针接收// char* q = &p;// &p + 1 == q + 1// &p + 1 就是跳过 p 指针变量后的地址printf("%d\n", sizeof(&p[0] + 1));// 4/8// p[0] 就是首元素// &p[0] 是首元素地址,&p[0] + 1是第二个元素地址// 地址大小是4/8字节return 0;
}

运行结果(x64):

代码6:

    char* p = "abcdef";
    printf("%d\n", strlen(p));
    // 6
    // 从‘a’开始读到 \0
    
    printf("%d\n", strlen(p + 1));
    // 5
    // 从‘b’开始读到 \0

    //printf("%d\n", strlen(*p));
    // err
    // *p 就是‘a’
    // 传给 stlen 的是第一个元素 a 的ASCII - 97
    // 越界访问

    //printf("%d\n", strlen(p[0]));
    // err
    // p[0] —— *(p+0) —— *p —— ‘a’
    // 传给 stlen 的是第一个元素 a 的ASCII - 97
    // 越界访问
    
    printf("%d\n", strlen(&p));
    // 随机值
    // &p 是从指针变量 p 的起始位置开始读,与常量字符串无关了
    // 不知道什么时候到达\0
    
    printf("%d\n", strlen(&p + 1));
    // 随机值
    // &p + 1 是从跳过指针变量 p 的位置开始读
    // 不知道什么时候到达\0
    
    printf("%d\n", strlen(&p[0] + 1));
    // 5
    // p[0] —— *(p+0) —— *p —— ‘a’
    // &p[0]就是‘a’又取了个地址,+ 1 则是‘b’
    // 就是从‘b’开始读到 \0

运行代码验证一下:

int main()
{char* p = "abcdef";printf("%d\n", strlen(p));// 6// 从‘a’开始读到 \0printf("%d\n", strlen(p + 1));// 5// 从‘b’开始读到 \0//printf("%d\n", strlen(*p));// err// *p 就是‘a’// 传给 stlen 的是第一个元素 a 的ASCII - 97// 越界访问//printf("%d\n", strlen(p[0]));// err// p[0] —— *(p+0) —— *p —— ‘a’// 传给 stlen 的是第一个元素 a 的ASCII - 97// 越界访问printf("%d\n", strlen(&p));// 随机值// &p 是从指针变量 p 的起始位置开始读,与常量字符串无关了// 不知道什么时候到达\0printf("%d\n", strlen(&p + 1));// 随机值// &p + 1 是从跳过指针变量 p 的位置开始读// 不知道什么时候到达\0printf("%d\n", strlen(&p[0] + 1));// 5// p[0] —— *(p+0) —— *p —— ‘a’// &p[0]就是‘a’又取了个地址,+ 1 则是‘b’// 就是从‘b’开始读到 \0return 0;
}

运行结果(x64):

3.二维数组

假设已知一个三行四列的数组,进行以下辨析:

    printf("%d\n", sizeof(a));
    // 48
    // a是数组名,计算的是数组的大小
    // 单位是字节,3*4*sizeof(int)=3*4*4=48
    
    printf("%d\n", sizeof(a[0][0]));
    // 4
    // a[0][0]是第一行第一个元素的大小,是4个字节

    

    printf("%d\n", sizeof(a[0]));
    // 16
    // a[0]是第一行的数组名
    // 数组名单独在sizeof内,计算的是数组的总大小
    // 第一行四个元素的大小 4*4=16

  

    printf("%d\n", sizeof(a[0] + 1));
    // 4/8
    // a[0]并没有单独放在sizeof内
    // a[0]是数组首元素的地址,就是a[0][0]的地址
    // +1后是a[0][1]的地址
    // 是地址就是4/8

    printf("%d\n", sizeof(*(a[0] + 1)));
    // 4
    // a[0] + 1后是a[0][1]的地址
    // *(a[0] + 1)是第一行第二个元素
    
    printf("%d\n", sizeof(a + 1));
    // 4/8
    // a 作为数组,并没有单独放在 sizeof 内
    // a 表示数组首元素的地址,是二维数组首元素
    // 第一行的地址,也就是a[1]的地址
    // +1表示跳过一行的地址
    // a + 1 是第二行的地址,是数组指针,是地址的大小
    // 是地址就是4/8

    printf("%d\n", sizeof(*(a + 1)));
    // 16
    // a + 1 是第二行的地址
    // *(a + 1) j就是第二行 
    // 计算的是第二行的大小3*4*4=16
    // 另一个理解方法
    // *(a + 1) = a[1]
    // a[1]是第二行的数组名
    // 相当于 sizeof(arr[1])
    // 意思是将数组名单独放在 sizeof 内部了
    // 同理,计算的是第二行的大小
    
    printf("%d\n", sizeof(&a[0] + 1));
    // 4/8
    // a[0] 是第一行的数组
    // &a[0] 取出的是数组的地址,是第一行的地址
    // &a[0] + 1,是第二行的地址
    // 是地址就是4/8

    printf("%d\n", sizeof(*(&a[0] + 1)));
    // 16
    // &a[0] + 1,是第二行的地址
    // *(&a[0] + 1)是对第二行的地址解引用,就是第二行
    // 计算的是第二行的大小 4*4=16
    
    printf("%d\n", sizeof(*a));
    // 16
    // a 作为数组,并没有单独放在 sizeof 内
    // a 表示数组首元素的地址,是二维数组首元素
    // 第一行的地址,也就是a[1]的地址
    // *a 解引用,就是第一行
    // 计算的是第一行的大小 4*4=16
    // 另一种理解方法*a == *(a + 0) == a[0]

    printf("%d\n", sizeof(a[3]));
    // a[3]是第四行,单独放在 sizeof 内
    // 其无需真实存在,只需要通过类型推导就可以计算出长度
    // 推到得出,是类似于 int [4] 的一个整型数组
    // 计算的是第四行的大小,4*4=16
    
    return 0;
}

代码运行验证一下:

int main()
{int a[3][4] = { 0 };printf("%d\n", sizeof(a));// 48// a是数组名,计算的是数组的大小// 单位是字节,3*4*sizeof(int)=3*4*4=48printf("%d\n", sizeof(a[0][0]));// 4// a[0][0]是第一行第一个元素的大小,是4个字节printf("%d\n", sizeof(a[0]));// 16// a[0]是第一行的数组名// 数组名单独在sizeof内,计算的是数组的总大小// 第一行四个元素的大小 4*4=16printf("%d\n", sizeof(a[0] + 1));// 4/8// a[0]并没有单独放在sizeof内// a[0]是数组首元素的地址,就是a[0][0]的地址// +1后是a[0][1]的地址// 是地址就是4/8printf("%d\n", sizeof(*(a[0] + 1)));// 4// a[0] + 1后是a[0][1]的地址// *(a[0] + 1)是第一行第二个元素printf("%d\n", sizeof(a + 1));// 4/8// a 作为数组,并没有单独放在 sizeof 内// a 表示数组首元素的地址,是二维数组首元素// 第一行的地址,也就是a[1]的地址// +1表示跳过一行的地址// a + 1 是第二行的地址,是数组指针,是地址的大小// 是地址就是4/8printf("%d\n", sizeof(*(a + 1)));// 16// a + 1 是第二行的地址// *(a + 1) j就是第二行 // 计算的是第二行的大小3*4*4=16// 另一个理解方法// *(a + 1) = a[1]// a[1]是第二行的数组名// 相当于 sizeof(arr[1])// 意思是将数组名单独放在 sizeof 内部了// 同理,计算的是第二行的大小printf("%d\n", sizeof(&a[0] + 1));// 4/8// a[0] 是第一行的数组// &a[0] 取出的是数组的地址,是第一行的地址// &a[0] + 1,是第二行的地址// 是地址就是4/8printf("%d\n", sizeof(*(&a[0] + 1)));// 16// &a[0] + 1,是第二行的地址// *(&a[0] + 1)是对第二行的地址解引用,就是第二行// 计算的是第二行的大小 4*4=16printf("%d\n", sizeof(*a));// 16// a 作为数组,并没有单独放在 sizeof 内// a 表示数组首元素的地址,是二维数组首元素// 第一行的地址,也就是a[1]的地址// *a 解引用,就是第一行// 计算的是第一行的大小 4*4=16// 另一种理解方法*a == *(a + 0) == a[0]printf("%d\n", sizeof(a[3]));// a[3]是第四行,单独放在 sizeof 内// 其无需真实存在,只需要通过类型推导就可以计算出长度// 推到得出,是类似于 int [4] 的一个整型数组// 计算的是第四行的大小,4*4=16return 0;
}

 运行结果(x64):

数组名的意义:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

相关文章:

  • 量化策略兼容性设计
  • Linux常用命令34——uname显示系统内核信息
  • AtCoder Beginner Contest 404 A-E 题解
  • QT中多线程的实现
  • 位运算题目:安排电影院座位
  • 编专利或委托他人编专利属于学术不端行为吗?
  • crawl4ai能替代scrapy等传统爬虫框架吗?
  • 【报错】view size is not compatible with input tensor‘s size and stride
  • 编译原理头歌实验:词法分析程序设计与实现(C语言版)
  • Oracle OCP认证考试考点详解083系列12
  • SpringBoot中使用MCP和通义千问来处理和分析数据-连接本地数据库并生成实体类
  • 15前端项目----用户信息/导航守卫
  • SNMP 协议介绍、开发方法及示例
  • 【造包工具】【Xcap】精讲Xcap构造分片包(IPv4、ipv6、4G\5G等pcap均可),图解超赞超详细!!!
  • 投资逻辑与未来风险:高端PCB的黄金周期能持续多久?
  • 【Linux网络】网络命令
  • mongodb升级、改单节点模式
  • 矢量网络分析仪测驻波比:从原理到实战操作全解析
  • Nacos源码—6.Nacos升级gRPC分析一
  • 【redis】分片方案
  • 央行设立服务消费与养老再贷款,额度5000亿元
  • 14岁女生瞒报年龄文身后洗不掉,法院判店铺承担六成责任
  • 云南一餐馆收购长江野生鱼加工为菜品,被查处罚款
  • 现场丨“影像上海”启幕:串联摄影、电影与当代艺术
  • “20后”比“60后”更容易遭遇极端气候事件
  • 国家发改委副主任谈民营经济促进法:以法治的稳定性增强发展的确定性