C语言提高
- CS和BS的区别
- 函数封装和数组形参退化为指针
- 数据类型本质
- 变量的本质
- 内存分区模型
-
- 全局区
-
- 以文字常量区为例分析全局区
- 栈区
- 堆区
- 函数的调用模型
- 函数调用变量传递分析
- 静态局部变量的使用
- 栈地址的生长方向
- 堆地址的生长方向
- 内存的存放方向-以数组为例
- 指针强化
-
- 强化一
-
- 指针也是数据类型
- 通过*来操作内存
- 易错点
-
- 写内存时,一定要确保内存可写
- 不允许向NULL和未知非法地址拷贝内存
- 强化二
-
- 怎样定义一个变量来保存自身的地址?
- 主调函数和被调函数
- 内存分配方式
-
- 输入特性
- 输出特性
- 字符串初始化
- 数组法和指针法操作字符串
- 自己写字符串拷贝函数
- 完善字符串拷贝函数
- 项目开发常用字符串应用模型
-
- strstr中的while和do-while模型
-
- do-while模型
- while模型封装成函数
- 两头堵模型
-
- 求两头堵模型下非空字符串的个数
- 求两头堵模型下非空字符串并输出
- const的使用
-
- 修饰一个变量为只读
-
- 修饰普通变量为只读
- 修饰普通指针变量为只读
- 修饰结构体变量为只读
- 如何引用另一个c文件中使用const修饰的变量
- C语言中const是一个冒牌货
- 二级指针
-
- 二级指针做函数参数---输出特性
- 二级指针做函数参数---输入特性
-
- 第一种输入模型
-
- 指针数组的定义与使用
- 对指针数组中的成员进行排序
- 对上述代码进行函数封装
- 第二种内存模型
-
- 封装成函数
- 第三种内存模型
-
- 导入
- 封装函数
- 封装函数改进-多级指针的使用
学习网址: https://www.bilibili.com/video/BV1Rt411m78c?p=8.
CS和BS的区别
文章图片
函数封装和数组形参退化为指针 因为数组做形参会退化为指针,所以,如果在被调函数中涉及到数组的长度的时候,只能把n作为参数传进去(只能在主函数中求出),而在被调函数中求不出来。
//如果数组作为函数参数,数组形参退化为指针。
//void print_array(int a[10], int n)
//void print_array(int a[1], int n)
//void print_array(int a[], int n)
//[]中的值写不写都行.
void print_array(int *a, int n)
{
//a,当作指针用,指针类型,32位,长度是4个字节
//n = 4 / 4 = 1
n = sizeof(a) / sizeof(a[0]);
//求数组中元素的个数
printf("print_array:n = %d.\n", n);
int i;
for (i = 0;
i < n;
i++)
{
printf("%2d ", a[i]);
}
printf("\n");
}
调用它
print_array(a, n);
//传数组名,就是数组的首地址。
不能填0
文章图片
写大于0的值都没关系,因为都是把它当作传入数组的首地址来操作的。所以干脆直接写成指针。
数据类型本质 数据类型的本质:固定内存块大小的别名
#include.h>
#include.h>
#include.h>int main(void)
{
int a[10];
//告诉编译器,分配40个字节
int b;
//告诉编译器,分配4个字节 /* 类型的本质:固定内存块大小的别名 */
printf("sizeof(a) = %d, sizeof(b) = %d \n", sizeof(a), sizeof(b));
/* 打印地址 */
// 数组名字,数组首元素地址,数组首地址
printf("a:%d,&a:%d \n",a,&a);
//a和&a的数组类型不一样
//a是数组首元素地址,一个元素是4字节,+1相当于加4个字节
//&a是整个数组的首地址,一个数组4*10 = 40字节,+1相当于加40个字节
printf("a+1:%d,&a+1:%d \n", a+1, &a+1);
//指针类型的长度,32位程序,长度是4
//64位程序,长度是8 char****************** p = NULL;
int* q = NULL;
printf("%d,%d\n",sizeof(p),sizeof(q));
system("pause");
return 0;
}
文章图片
变量的本质 变量本质:一段连续内存空间的别名
变量相当于门牌号,内存相当于房间
内存分区模型 C++程序在执行时,将内存大方向划分为4个区域
? 代码区:存放函数体的二进制代码,由操作系统进行管理的
? 全局区:存放全局变量和静态变量以及常量
? 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
? 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
全局区 以文字常量区为例分析全局区
#include.h>
#include.h>
#include.h>char* get_str1()
{// p 是栈区
char* p = "abcdefg";
//"abcdefg\0"放在文字常量区(全局区)
return p;
}char* get_str2()
{
char* q = "abcdefg";
//文字常量区(全局区)
return q;
}
int main(void)
{
char* p = NULL;
char* q = NULL;
p = get_str1();
q = get_str2();
//%s:指针指向内存区域的内容
//%d:打印p本身的值
printf("p = %s,p = %d\n",p,p);
printf("q = %s,q = %d\n",q,q);
system("pause");
return 0;
}
搞错的点:
//%s:指针指向内存区域的内容
//%d:打印p本身的值
printf(“p = %s,p = %d\n”,p,p);
运行结果:p、q指向同一块内存。
文章图片
画图分析:
文章图片
栈区 与上面的有区别的,上面是指针,这里是数组。
#include.h>
#include.h>
#include.h>char* get_str()
{
char str[] = "abcdefg";
return str;
}int main(void)
{
char buf[128] = { 0 };
strcpy(buf, get_str());
//%s:指针指向内存区域的内容
//%d:打印p本身的值
printf("buf = %s.\n",buf);
system("pause");
return 0;
}
"abcdefg\0"在全局区。
画图分析:
文章图片
运行结果:
文章图片
从这里看是仍保留着之前的内容。但是,如果是在一个很大的程序中,就有可能存在着程序崩溃的隐患。或者在不同的编译器中,输出的结果有可能是乱码。
这个例子还不太好说明问题,因为有可能strcpy之后,get_str才释放,这样就避免了我们想要凸显的问题了。
我们对这个程序进行改进下。
#include.h>
#include.h>
#include.h>char* get_str()
{
char str[] = "abcdefg";
return str;
}int main(void)
{
char* p = NULL;
p = get_str();
//%s:指针指向内存区域的内容
printf("p = %s.\n", p);
system("pause");
return 0;
}
运行结果:
文章图片
画图分析:
文章图片
堆区
#include.h>
#include.h>
#include.h>char* get_str()
{
char *tmp = (char*)malloc(100);
if (tmp == NULL)
{
return NULL;
}
strcpy(tmp, "abcdefg");
return tmp;
}int main(void)
{
char* p = NULL;
p = get_str();
if (p != NULL)
{
//%s:指针指向内存区域的内容
printf("p = %s.\n", p);
free(p);
// 解除p与堆区的指向关系,告诉操作系统可以让别人来操作这块区域了。堆区内容仍然存在。
p = NULL;
//虽然解除指向关系之后,但p默认还是会指向堆区的。让p指向NULL,彻底脱离关系。
} printf("\n");
system("pause");
return 0;
}
运行结果:
文章图片
画图分析:
文章图片
函数的调用模型
文章图片
函数调用变量传递分析 1、fun()和fun2()都在main函数中
文章图片
2、
文章图片
3、fun2(a)嵌套在fun1()中。
文章图片
4、
文章图片
5、
文章图片
静态局部变量的使用 静态局部变量放在全局区。
栈地址的生长方向 栈地址的生长方向是向下(递减)的。
文章图片
堆地址的生长方向 堆地址的生长方向是向上(递增)的。
#include.h>
#include.h>
#include.h>int main(void)
{
int* c = (int*)malloc(4);
int* d = (int*)malloc(4);
printf("c = %d,d = %d\n", c, d);
printf("\n");
system("pause");
return 0;
}
文章图片
内存的存放方向-以数组为例 给数组分配一块内存空间。
文章图片
画图示意:
文章图片
指针强化 强化一 指针也是数据类型
指针也是数据类型,指针变量也是一种变量。
文章图片
画图示意怎么画。
文章图片
通过*来操作内存
#include.h>
#include.h>
#include.h>int main(void)
{
int a = 100;
int* p1 = NULL;
//指针指向谁,就把谁的地址赋值给指针
p1 = &a;
//*钥匙,通过*可以找到指针指向的内存区域,操作的是内存
*p1 = 22;
// *放在“=”左边,给内存赋值,写内存
// *放在“=”右边,取内存的值,读内存
int b = *p1;
printf(" b = %d\n",b);
printf("\n");
system("pause");
return 0;
}
易错点
写内存时,一定要确保内存可写
#include.h>
#include.h>
#include.h>int main(void)
{
//写内存时,一定要确保内存可写
char buf[] = "abcd";
buf[2] = 'a';
printf("buf = %s",buf);
printf("\n");
system("pause");
return 0;
}
字符串从字符常量区复制到了buf数组中。所以修改不会出错。
文章图片
直接操作内存常量区时,会报错。
不允许向NULL和未知非法地址拷贝内存
文章图片
文章图片
强化二 理解指针必须和内存四区相结合
怎样定义一个变量来保存自身的地址?
答:在原来类型基础上加一个*
int main(void)
{
//一个变量,应该定义一个怎样类型的指针保存它的地址
//在原来类型基础上加一个*
inta = 10;
int*p = &a;
int** q = &p;
int******* t = NULL;
int******** u = &t;
printf("\n");
system("pause");
return 0;
}
主调函数和被调函数
主调函数可把堆区、栈区、全局数据内存地址传给被调用函数。
被调函数只能返回堆区和全局数据。
(前面 “函数调用变量传递分析” 也提到过)
内存分配方式
指针做函数参数,是有输入和输出特性的
文章图片
输入特性
#include.h>
#include.h>
#include.h>/* 指针做函数参数 -- 输入特性 *///参数列表中写 /* in */ ,表示有指向的内存区域,
//且这个内存区域在主调函数中分配。
//这里所指向的内存区域就是buf
void fun(char* p /* in */)//指针做函数参数
{
//给p所指向的内存区域拷贝
strcpy(p,"abcdefg");
}void fun2(char* p /* in */)//指针做函数参数
{
if (p == NULL)
{
return;
}
//给p所指向的内存区域拷贝
strcpy(p, "abcdefg");
}int main(void)
{
//输入,主调函数分配内存
char buf[100] = {0};
//分配好空间了
fun(buf);
//把首地址传过去
printf("buf = %s\n", buf);
char* str = NULL;
fun2(str);
printf("\n");
system("pause");
return 0;
}
输出特性
#include.h>
#include.h>
#include.h>/* 指针做函数参数 -- 输出特性 */
void fun(char** p /* out */, int*len)
{
//先判断是否为空
if (p == NULL)
{
return;
} char* tmp = (char*)malloc(100);
if (tmp == NULL)//说明动态数组开辟失败
{
return;
} strcpy(tmp,"abcdefg");
//间接赋值
*p = tmp;
*len = strlen(tmp);
}int main(void)
{
//输出,被调用函数分配内存,必须地址传递,否则不能影响实参
char* p = NULL;
int len = 0;
fun(&p,&len);
if (p != NULL)
{
printf("p=%s,len=%d.\n",p,len);
} printf("\n");
system("pause");
return 0;
}
画图分析,很重要。
文章图片
字符串初始化
#include.h>
#include.h>
#include.h>/* C语言没有字符串类型,通过字符数据模拟
C语言字符串,以字符'\0'、数字0结尾。
*/int main(void)
{
//不指定长度,没有'\0'结束符,有多少个元素就有多长
char buf[] = { 'a','b','c'};
printf("buf = %s\n",buf);
//指定长度,后面没有赋值的元素,自动补0
char buf1[100] = { 'a','b','c' };
printf("buf1 = %s\n", buf1);
char buf2[100] = { 'a','b','c','0','7' };
printf("buf2 = %s\n", buf2);
char buf3[100] = { 'a','b','c',0 ,'7' };
printf("buf3 = %s\n", buf3);
char buf4[100] = { 'a','b','c','\0' ,'7' };
printf("buf4 = %s\n", buf4);
//使用字符串初始化,常用
char buf5[] = "abc";
printf("buf5 = %s\n", buf5);
//strlen:测字符串长度,不包含数字0或字符'\0',
//sizeof:测数组长度,包括数字0或字符'\0'。
printf("strlen(buf5) = %d ,sizeof(buf5) = %d\n", strlen(buf5), sizeof(buf5));
char buf6[100] = "abc";
//strlen:测字符串长度,不包含数字0或字符'\0',
//sizeof:测数组长度,包括数字0或字符'\0'。
printf("strlen(buf6) = %d ,sizeof(buf6) = %d\n", strlen(buf6), sizeof(buf6));
printf("\n");
system("pause");
return 0;
}
输出结果:
文章图片
数组法和指针法操作字符串
文章图片
自己写字符串拷贝函数
文章图片
简化:
文章图片
简化
文章图片
最终的简洁写法:
文章图片
完善字符串拷贝函数
文章图片
必须的:判断形参指针是否为NULL。
不是必须的:(改变首地址时使用(比如(str++)),移动时不使用(比如(str[begin],bigen变)))最好不要直接使用形参,而是要添加辅助变量,这样可以在调试或打印输出时保证指针指向实参的首地址。
项目开发常用字符串应用模型 strstr中的while和do-while模型 利用strstr标准库函数找出一个字符串中substr出现的个数。
do-while模型
#include.h>
#include.h>
#include.h>int main(void)
{
char* p = "abcdefgabcdegfabcdefgh";
int n = 0;
do
{
p = strstr(p,"abcd");
if (p != NULL)
{
n++;
p += strlen("abcd");
}
else //如果没有匹配的字符串,跳出循环
{
break;
}
} while (*p != '\0');
printf("n = %d.\n", n);
printf("\n");
system("pause");
return 0;
}
while模型封装成函数
#include.h>
#include.h>
#include.h>int my_strstr(char* str, char* substr,int* n)
{
//辅助变量
int i = 0;
char* tmp_str = str;
char* tmp_substr = substr;
//strstr函数返回匹配到的字符串的首地址
while ((tmp_str = strstr(tmp_str, tmp_substr)) != NULL)
{
//能进来,肯定有匹配的子串
i++;
//重新设置起点位置
tmp_str += strlen(tmp_substr);
if (*tmp_str == 0)
{
break;
}
}
//间接赋值
*n = i;
return 0;
}int main(void)
{
char* str = "sdabcdefgabcdegfabcd";
char* substr = "abcd";
int n = 0;
int ret = 0;
ret = my_strstr(str, substr ,&n);
//通过形参改实参的值,要使用地址传递。
if (!ret)
{
printf("n = %d.\n", n);
}
printf("\n");
system("pause");
return 0;
}
两头堵模型 求两头堵模型下非空字符串的个数
#include.h>
#include.h>
#include.h>
#include /* isspace(测试字符是否为空格字符)
* 头文件 #include
* 函数int isspqce(int c)
* return:若参数c为空格就返回TRUE,否则返回NULL(0)。
*/int my_isspace(char* str, int* n)
{
if(str == NULL || n ==NULL)
{
return -1;
}
int begin = 0;
int end = strlen(str ) - 1;
//记得要减一 // 从左开始
// 如果当前字符为空且没有结束
//或者直接判断(str [begin] =='')
while (isspace(str [begin]) && str [begin] != 0)
{
begin++;
//往右移动
} while (isspace(str [end]) && str [end] != 0)
{
end--;
//往左移动
} *n = end - begin + 1;
return 0;
}int main(void)
{
// 两头堵,两头为空格
char* str = "cdegfabcd";
int n = 0;
int ret = 0;
ret = my_isspace(str, &n);
if (!ret)
{
printf("n = %d\n", n);
}
printf("\n");
system("pause");
return 0;
}
求两头堵模型下非空字符串并输出
#include.h>
#include.h>
#include.h>
#include /* isspace(测试字符是否为空格字符)
* 头文件 #include
* 函数int isspqce(int c)
* return:若参数c为空格就返回TRUE,否则返回NULL(0)。
*/int my_isspace(char* str, char* buf)
{
if (str == NULL || buf == NULL)
{
return -1;
} int begin = 0;
int end = strlen(str) - 1;
//记得要减一
int n = 0;
// 从左开始
// 如果当前字符为空且没有结束
//或者直接判断(str[begin] =='')
while (isspace(str[begin]) && str[begin] != 0)
{
begin++;
//往右移动
} while (isspace(str[end]) && str[end] != 0)
{
end--;
//往左移动
} n = end - begin + 1;
strcpy(buf, str+begin,n);
buf[n] = '\0';
return 0;
}int main(void)
{
// 两头堵,两头为空格
char* str = "cdegfabcd";
char buf[100] = {0};
int n = 0;
int ret = 0;
ret = my_isspace(str, buf);
if (!ret)
{
printf("buf = %s\n", buf);
}
printf("\n");
system("pause");
return 0;
}
const的使用 修饰一个变量为只读 const修饰的变量,定义时需要初始化。
修饰普通变量为只读
#include.h>
#include.h>
#include.h>int main(void)
{
//const :修饰一个变量为只读
const int a = 10;
a = 10;
//err,a由const修饰,不能修改。
}
修饰普通指针变量为只读
const char *p = buf;
char *const p1 = buf;
const char *const p2 = buf;
#include.h>
#include.h>
#include.h>int main(void)
{
//指针变量和指针指向的内存(中的内容)是两个概念
char buf[] = "abcdefg";
char buf_b[] = "abcdefg_b";
//从左往右看,跳过数据类型,看修饰哪个字符
//如果是*,说明指针指向的内存(中的内容)不能改变
//如果是指针变量,说明指针的指向不能改变,指针的值不能改变
//跳过数据类型char,const修饰的是*,所以指针指向的内存(中的内容不能改变)
const char* p = buf;
//与 char const* p = buf;
等价
//也就是说*(p+i)='f' 或者 p[i] ='f';
是错误的。
//但是指针的指向是可以变的,或者说指针的值可以改变。
//比如:
p = buf_b;
//同样的,
char* const p1 = buf;
//p1 = buf_b;
//错误
p1[2] = 'f';
//都不能变
const char* const p2 = buf;
printf("\n");
system("pause");
return 0;
}
修饰结构体变量为只读
#include.h>
#include.h>
#include.h>#if 0
int main(void)
{
//指针变量和指针指向的内存(中的内容)是两个概念
char buf[] = "abcdefg";
char buf_b[] = "abcdefg_b";
//从左往右看,跳过数据类型,看修饰哪个字符
//如果是*,说明指针指向的内存(中的内容)不能改变
//如果是指针变量,说明指针的指向不能改变,指针的值不能改变
//跳过数据类型char,const修饰的是*,所以指针指向的内存(中的内容不能改变)
const char* p = buf;
//与 char const* p = buf;
等价
//也就是说*(p+i)='f' 或者 p[i] ='f';
是错误的。
//但是指针的指向是可以变的,或者说指针的值可以改变。
//比如:
p = buf_b;
//同样的,
char* const p1 = buf;
//p1 = buf_b;
//错误
p1[2] = 'f';
//都不能变
const char* const p2 = buf;
printf("\n");
system("pause");
return 0;
}
#endiftypedef struct Mystruct
{
int a;
int b;
}Mystruct;
void fun1(Mystruct * p)
{
//指针能变
p = NULL;
//指针指向的内存(中的内容)也可以变
p->a = 10;
}void fun2(Mystruct const *p)
{
//指针能变
p = NULL;
//指针指向的内存(中的内容)不可以变
//p->a = 10;
err
}void fun3( Mystruct * const p)
{
//指针不能变
//p = NULL;
err
//指针指向的内存(中的内容)可以变
p->a = 10;
}//p只读
void fun4(const Mystruct * const p)
{
//指针不能变
//p = NULL;
//指针指向的内存(中的内容)也不可以变
//p->a = 10;
Mystruct tmp;
tmp.a = p->a;
}int main(void)
{
printf("\n");
system("pause");
return 0;
}
如何引用另一个c文件中使用const修饰的变量
文章图片
在const.c中
文章图片
在另一个.c中
文章图片
运行结果:
文章图片
C语言中const是一个冒牌货
为什么说它是冒牌的呢?
是因为虽然我们不能直接修改被const修饰的变量的值,但是我们可以通过间接的方式进行修改。
#include.h>
#include.h>
#include.h>int main(void)
{
const int b = 10;
//b = 100;
//erro
int* q = &b;
*q = 100;
printf("b = %d",b);
printf("\n");
system("pause");
return 0;
}
运行结果如下:
文章图片
二级指针 如果 一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为二级指针。
二级指针做函数参数—输出特性
#include.h>
#include.h>
#include.h>int getmem(char** p)
{
char *tmp = (char*)malloc(sizeof(char) * 100);
if (tmp == NULL)
{
return -1;
}
strcpy(tmp,"abcdefg");
*p = tmp;
return 0;
}int main(void)
{
char* p = NULL;
int ret = 0;
ret = getmem(&p);
if (ret == 0)
{
printf("p = %s", p);
}
if (p != NULL)
{
free(p);
p = NULL;
} printf("\n");
system("pause");
return 0;
}
画图分析:
文章图片
注意:
1、地址传递,形参修改会影响到实参,所以要定义一个临时变量。
2、输出特性,在被调函数中分配空间。使用malloc后,在被调函数结束后要释放。
二级指针做函数参数—输入特性 第一种输入模型
指针数组的定义与使用
#include.h>
#include.h>
#include.h>int main(void)
{
//每个类型都是char*
char *p0 = "0000";
char *p1 = "1111";
char *p2 = "2222";
char *p3 = "3333";
//换成用指针数组表示。两种表示,本质一样 //指针数组,指针的数组,他是一个数组,每一个元素都是指针char *
char* p[] = { "0000" ,"1111" ,"2222" ,"3333" };
for (int i = 0;
i < 4;
i++)
{
printf("%s\n",p[i]);
} printf("\n");
system("pause");
return 0;
}
画图说明:
文章图片
改进代码,变的灵活一些。
特别注意:这种改进是有条件的,需要在定义指针数组时不指定长度。
也就是char* p[]
而char* p[10]这样指定长度不行。
//改进代码,变的灵活一些
int n = sizeof(p) / sizeof(p[0]);
for (int i = 0;
i < n;
i++)
{
printf("%s\n",p[i]);
}
对指针数组中的成员进行排序
#include.h>
#include.h>
#include.h>int main(void)
{
//指针数组,指针的数组,他是一个数组,每一个元素都是指针char *
char* p[] = { "aabb","0000" ,"cccc","1111" ,"2222","4444" ,"3333" };
int n = sizeof(p) / sizeof(p[0]);
printf("数组中元素的个数:%d\n",n);
// 7 int i = 0;
int j = 0;
char* tmp = NULL;
printf("排序前:\n");
for ( i = 0;
i <= n-1;
i++)
{
printf("%s,",p[i]);
} //比较排序,按每个字符串中的字符的大小来升序排列。
for (i = 0;
i < n - 1;
i++)
{
for (j = i + 1;
j < n;
j++)
{
//strcmp是字符串比较函数。
//strcmp(a,b)
//字符a>b时,输出>0,相等时,输出0,小于时,输出<0
if (strcmp(p[i], p[j]) > 0)
{
tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
}
} printf("\n排序后:\n");
for (i = 0;
i <= n - 1;
i++)
{
printf("%s,", p[i]);
} printf("\n");
system("pause");
return 0;
}
需要特别注意的一个地方。
文章图片
对上述代码进行函数封装 这里面有一个过渡非常重要。
文章图片
因此,
#include.h>
#include.h>
#include.h>// 对于形参,实参是什么类型,形参写跟它一样
//void printf_array(char* p[], int n)
void printf_array(char** p, int n)
{
int i = 0;
for (i = 0;
i <= n - 1;
i++)
{
printf("%s,", p[i]);
}
printf("\n");
}void sort_array(char** p, int n)
{
int i = 0;
int j = 0;
char* tmp = NULL;
//比较排序,按每个字符串中的字符的大小来升序排列。
for (i = 0;
i < n - 1;
i++)
{
for (j = i + 1;
j < n;
j++)
{
//strcmp是字符串比较函数。
//函数原型:int strcmp(const char *s1,const char *s2);
//参数:s1、s2是指向字符串的指针
//返回值: 自左向右逐个按照ASCII码值进行比较,直到出现不同的字符或遇’\0’为止。
//如果返回值 < 0,则表示 s1 小于 s2。
//如果返回值 > 0,则表示 s1 大于 s2。
//如果返回值 = 0,则表示 s1 等于 s2。if (strcmp(p[i], p[j]) > 0)
{
tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
}
}
}int main(void)
{
//指针数组,指针的数组,他是一个数组,每一个元素都是指针char *
char* p[] = { "aabb","0000" ,"cccc","1111" ,"2222","4444" ,"3333" };
int n = sizeof(p) / sizeof(p[0]);
printf("排序前:\n");
printf_array(p,n);
sort_array(p, n);
printf("排序后:\n");
printf_array(p, n);
printf("\n");
system("pause");
return 0;
}
第二种内存模型
#include.h>
#include.h>
#include.h>int main(void)
{
//
char a0[5] = "aabb";
char a1[5] = "0000";
char a2[5] = "cccc";
char a3[5] = "1111";
//写法本质一样
//二维数组a[4][5]可以看作是4个a[5]的一维数组,
char a[4][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
//a[0] = "aabb";
int i = 0;
for (i = 0;
i < 4;
i++)
{
//下面三个输出结果一样
printf("%s\n", a + i);
printf("%s\n", a[i]);
printf("%s\n",*(a+i));
} printf("\n");
system("pause");
return 0;
}
概念辨析和画图分析:
文章图片
求二维数组中一维数组的个数(行数):
int n = sizeof(a) / sizeof(a[0]);
封装成函数 前面我们分析了,对于二维数组名a应该理解成是首行地址,而且a+1应该加一行,如果该行(一维数组)的长度为n,那么a+1应该加n*sizeof(数据类型)个字节。比如是char类型,n为5,那么就应该加5。
我们在定义被调函数的参数类型的时候,最最简单的方式就是主调函数的参数类型和被调参数类型定义一致。
主调函数的变量变量定义为为:
char a[][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
被调函数的参数定义:
void print_array(char a[][5], int n)
被调函数参数定义绝对不能是下面两种:
void print_array(char **a, int n)
void print_array(char* a[], int n)
为什么呢?
实际上,这两种写法本质上是一回事。都是指针数组。前面也提到了指针数组每一个元素都是指针char *,加1相当于加4个字节。
而我们这里加1应该加n个字节。所有不可以这样定义形参。
我们可以验证下。是不是这样。
这里被调函数中的实参为:
char a[][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
a+1相当于加5个字节。
验证如下:
文章图片
加4个字节
文章图片
加4个字节
文章图片
加5个字节
这里还有特别重要的一点,在指针数组中交换的是指针的指向。
而在这里,我们只能交换内存块。
交换的是内存块,而不是指针的指向。
因为这里的a[i],*(a+i)本身都是固定的地址值(都是常量),常量是不能修改的。
所以要使用strcpy来交换内存块。
代码如下:
#include.h>
#include.h>
#include.h>void print_array(char a[][5], int n)
{
int i = 0;
for (i = 0;
i < n;
i++)
{
//下面三个输出结果一样
//printf("%s, ", a + i);
printf("%s, ", a[i]);
// printf("%s, ",*(a+i));
}
printf("\n");
}void soft_array(char a[][5], int n)
{
int i ,j;
char tmp[5];
for (i = 0;
i < n - 1;
i++)
{
for (j = i + 1;
j < n;
j++)
{
if (strcmp(a[i], a[j]) > 0)
{
//交换的是内存块,而非指针的指向
strcpy(tmp, a[i]);
strcpy(a[i], a[j]);
strcpy(a[j], tmp);
}
}
}
}int main(void)
{
//二维数组a[4][5]可以看作是4个a[5]的一维数组,
char a[][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
int n = sizeof(a) / sizeof(a[0]);
printf("排序前:\n");
print_array(a, n);
soft_array(a, n);
printf("排序后:\n");
print_array(a, n);
printf("\n");
system("pause");
return 0;
}
第三种内存模型
导入 静态分配一个空间,并拷贝字符串。
char p0_sta[100] = { 0 };
strcpy(p0_sta, "abcd");
printf("%s\n", p0_sta);
动态分配一个空间,并拷贝字符串,现在不释放。
char* p0_dyn = NULL;
p0_dyn = (char *)malloc(100);
strcpy(p0_dyn,"abcd");
printf("%s\n", p0_dyn);
动态分配十个上述空间,并拷贝字符串,现在不再释放。
//10个char *,每个值都是空(NULL)
int i = 0;
char* p[3] = {0};
for (i = 0;
i < 3;
i++)
{
p[i] = (char*)malloc(100);
strcpy(p[i], "abcd");
}
能不能换一种方式(不用for循环),来分配上述空间呢?
再举个例子,
(其实这里可以不用举int的例子,直接用上面的char的例子,但是使用int可以突出开辟空间时所占内存大小的问题)
静态分配a[10]空间大小的内存
int a[10];
动态分配一个等价于a[10]空间大小的内存
int* q = (int *)malloc(10*sizeof(int));
if (q == NULL)
{
return -1;
}
现在通过类比来模仿上面分配一个等价于char* p[3]空间大小的内存,
int n = 3;
char** buf = (char **)malloc(n * sizeof(char*));
if (buf == NULL)
{
return -1;
}
这样分配好之后,就相当于定义了char *p[3]。
画图来分析一下:
文章图片
这样空间分配好以后,问题又来了。
我们能否直接进行字符串拷贝操作:
for (i = 0;
i < n;
i++)
{
strcpy(buf[i], "abcd");
}
答案是不可以的。我们完成这一步只是相当于
char* buf[3] = {0};
也就是说,p[i] = NULL,而写内存时是不允许向NULL和未知非法地址拷贝内存
因此,还需要在堆区开辟空间,需要注意类型。
for (i = 0;
i < n;
i++)
{
buf[i] = (char*)malloc(100);
strcpy(buf[i], "abcd");
}
画图来说明:
文章图片
最后,还要释放堆内存,先释放p[i]所指向的堆内存,然后再释放buf所指向的对内存。
//先释放buf[i]所指向的堆内存
for (i = 0;
i < n;
i++)
{
free(buf[i]);
buf[i] = NULL;
}
//再释放buf所指向的堆内存
if (buf != NULL)
{
free(buf);
buf = NULL;
}
封装函数
#include.h>
#include.h>
#include.h>char** getMem(int n)
{
int i;
char** buf = (char**)malloc(n * sizeof(char*));
if (buf == NULL)
{
return NULL;
} for (i = 0;
i < n;
i++)
{
buf[i] = (char*)malloc(100);
strcpy(buf[i], "abcdefg");
}
return buf;
}
void print_array(char** buf,int n)
{
int i;
for (i = 0;
i < n;
i++)
{
printf("%s\n", buf[i]);
}
}void freeMem(char** buf, int n)
{
int i;
//先释放buf[i]所指向的堆内存
for (i = 0;
i < n;
i++)
{
free(buf[i]);
buf[i] = NULL;
}
//再释放buf所指向的堆内存
if (buf != NULL)
{
free(buf);
buf = NULL;
}
}int main(void)
{
int i;
char** buf = NULL;
int n = 3;
buf = getMem(n);
print_array(buf,n);
//解除堆内的指向关系
freeMem(buf, n);
//值传递,形参不影响实参
buf = NULL;
printf("\n");
system("pause");
return 0;
}
画图分析:
文章图片
封装函数改进-多级指针的使用 参考输出特性。增加临时指针变量
#include.h>
#include.h>
#include.h>int getMem(char*** tmp,int n)
{
int i;
char** buf = (char**)malloc(n * sizeof(char*));
if (buf == NULL)
{
return NULL;
} for (i = 0;
i < n;
i++)
{
buf[i] = (char*)malloc(100);
strcpy(buf[i], "abcdefg");
} *tmp = buf;
return 0;
}
void print_array(char** buf,int n)
{
int i;
for (i = 0;
i < n;
i++)
{
printf("%s\n", buf[i]);
}
}int freeMem(char*** tmp, int n)
{
int i;
char** buf = *tmp;
//先释放buf[i]所指向的堆内存
for (i = 0;
i < n;
i++)
{
free(buf[i]);
buf[i] = NULL;
}
//再释放buf所指向的堆内存
if (buf != NULL)
{
free(buf);
buf = NULL;
} *tmp = NULL;
return 0;
}int main(void)
{
int i;
char** buf = NULL;
int n = 3;
getMem(&buf,n);
print_array(buf,n);
//解除堆内的指向关系
freeMem(&buf, n);
printf("\n");
system("pause");
return 0;
}
【C语言提高(一)】分配分析:
文章图片
释放分析:
文章图片
推荐阅读
- 笔记|嵌入式网络的基础知识
- Quant|量化分析师的Python日记【Q Quant兵器谱之函数插值】
- C语言|qsort库函数详解
- C语言|库函数《qsort》的模拟实现,原来如此简单
- c语言初学之路|【C语言】模拟实现库函数 strcpy(复制字符串内容) 与 strlen(求字符串长度)
- 千里之行始于足下|C语言qsort库函数的讲解
- 千里之行始于足下|C语言strcat库函数讲解
- 千里之行始于足下|C语言strcmp库函数讲解
- c语言|C语言中库函数的模拟实现