学习网址:
https://www.bilibili.com/video/BV1Rt411m78c?p=8.
CS和BS的区别

函数封装和数组形参退化为指针
因为数组做形参会退化为指针,所以,如果在被调函数中涉及到数组的长度的时候,只能把n作为参数传进去(只能在主函数中求出),而在被调函数中求不出来。
void print_array(int *a, int n)
{
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<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
int a[10];
int b;
printf("sizeof(a) = %d, sizeof(b) = %d \n", sizeof(a), sizeof(b));
printf("a:%d, &a:%d \n",a,&a);
printf("a+1:%d, &a+1:%d \n", a+1, &a+1);
char****************** p = NULL;
int* q = NULL;
printf("%d,%d\n",sizeof(p),sizeof(q));
system("pause");
return 0;
}

变量的本质
变量本质:一段连续内存空间的别名 变量相当于门牌号,内存相当于房间
内存分区模型
C++程序在执行时,将内存大方向划分为4个区域 ? 代码区:存放函数体的二进制代码,由操作系统进行管理的 ? 全局区:存放全局变量和静态变量以及常量 ? 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等 ? 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
全局区
以文字常量区为例分析全局区
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str1()
{
char* p = "abcdefg";
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();
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<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str()
{
char str[] = "abcdefg";
return str;
}
int main(void)
{
char buf[128] = { 0 };
strcpy(buf, get_str());
printf("buf = %s.\n",buf);
system("pause");
return 0;
}
"abcdefg\0"在全局区。 画图分析:  运行结果:  从这里看是仍保留着之前的内容。但是,如果是在一个很大的程序中,就有可能存在着程序崩溃的隐患。或者在不同的编译器中,输出的结果有可能是乱码。
这个例子还不太好说明问题,因为有可能strcpy之后,get_str才释放,这样就避免了我们想要凸显的问题了。
我们对这个程序进行改进下。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str()
{
char str[] = "abcdefg";
return str;
}
int main(void)
{
char* p = NULL;
p = get_str();
printf("p = %s.\n", p);
system("pause");
return 0;
}
运行结果:  画图分析: 
堆区
#include<stdio.h>
#include<stdlib.h>
#include<string.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)
{
printf("p = %s.\n", p);
free(p);
p = NULL;
}
printf("\n");
system("pause");
return 0;
}
运行结果:  画图分析: 
函数的调用模型

函数调用变量传递分析
1、fun()和fun2()都在main函数中  2、  3、fun2(a)嵌套在fun1()中。  4、  5、 
静态局部变量的使用
静态局部变量放在全局区。
栈地址的生长方向
栈地址的生长方向是向下(递减)的。 
堆地址的生长方向
堆地址的生长方向是向上(递增)的。
#include<stdio.h>
#include<stdlib.h>
#include<string.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<stdio.h>
#include<stdlib.h>
#include<string.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<stdio.h>
#include<stdlib.h>
#include<string.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)
{
int a = 10;
int* p = &a;
int** q = &p;
int******* t = NULL;
int******** u = &t;
printf("\n");
system("pause");
return 0;
}
主调函数和被调函数
主调函数可把堆区、栈区、全局数据内存地址传给被调用函数。 被调函数只能返回堆区和全局数据。 (前面 “函数调用变量传递分析” 也提到过)
内存分配方式
指针做函数参数,是有输入和输出特性的 
输入特性
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void fun(char* p )
{
strcpy(p,"abcdefg");
}
void fun2(char* p )
{
if (p == NULL)
{
return;
}
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<stdio.h>
#include<stdlib.h>
#include<string.h>
void fun(char** p , 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<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
char buf[] = { 'a','b','c'};
printf("buf = %s\n",buf);
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);
printf("strlen(buf5) = %d ,sizeof(buf5) = %d\n", strlen(buf5), sizeof(buf5));
char buf6[100] = "abc";
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<stdio.h>
#include<stdlib.h>
#include<string.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<stdio.h>
#include<stdlib.h>
#include<string.h>
int my_strstr(char* str, char* substr,int* n)
{
int i = 0;
char* tmp_str = str;
char* tmp_substr = substr;
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<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <ctype.h>
int my_isspace(char* str, int* n)
{
if(str == NULL || n ==NULL)
{
return -1;
}
int begin = 0;
int end = strlen(str ) - 1;
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<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <ctype.h>
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;
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<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
const int a = 10;
a = 10;
}
修饰普通指针变量为只读
const char *p = buf; char *const p1 = buf; const char *const p2 = buf;
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
char buf[] = "abcdefg";
char buf_b[] = "abcdefg_b";
const char* p = buf;
p = buf_b;
char* const p1 = buf;
p1[2] = 'f';
const char* const p2 = buf;
printf("\n");
system("pause");
return 0;
}
修饰结构体变量为只读
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#if 0
int main(void)
{
char buf[] = "abcdefg";
char buf_b[] = "abcdefg_b";
const char* p = buf;
p = buf_b;
char* const p1 = buf;
p1[2] = 'f';
const char* const p2 = buf;
printf("\n");
system("pause");
return 0;
}
#endif
typedef struct Mystruct
{
int a;
int b;
}Mystruct;
void fun1(Mystruct * p)
{
p = NULL;
p->a = 10;
}
void fun2(Mystruct const *p)
{
p = NULL;
}
void fun3( Mystruct * const p)
{
p->a = 10;
}
void fun4(const Mystruct * const p)
{
Mystruct tmp;
tmp.a = p->a;
}
int main(void)
{
printf("\n");
system("pause");
return 0;
}
如何引用另一个c文件中使用const修饰的变量
 在const.c中  在另一个.c中  运行结果: 
C语言中const是一个冒牌货
为什么说它是冒牌的呢? 是因为虽然我们不能直接修改被const修饰的变量的值,但是我们可以通过间接的方式进行修改。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
const int b = 10;
int* q = &b;
*q = 100;
printf("b = %d",b);
printf("\n");
system("pause");
return 0;
}
运行结果如下: 
二级指针
如果 一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为二级指针。
二级指针做函数参数—输出特性
#include<stdio.h>
#include<stdlib.h>
#include<string.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<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
char *p0 = "0000";
char *p1 = "1111";
char *p2 = "2222";
char *p3 = "3333";
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<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
char* p[] = { "aabb","0000" ,"cccc","1111" ,"2222","4444" ,"3333" };
int n = sizeof(p) / sizeof(p[0]);
printf("数组中元素的个数:%d\n",n);
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++)
{
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<stdio.h>
#include<stdlib.h>
#include<string.h>
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++)
{
if (strcmp(p[i], p[j]) > 0)
{
tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
}
}
}
int main(void)
{
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<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
char a0[5] = "aabb";
char a1[5] = "0000";
char a2[5] = "cccc";
char a3[5] = "1111";
char a[4][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
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<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_array(char a[][5], int n)
{
int i = 0;
for (i = 0; i < n; 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)
{
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);
动态分配十个上述空间,并拷贝字符串,现在不再释放。
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所指向的对内存。
for (i = 0; i < n; i++)
{
free(buf[i]);
buf[i] = NULL;
}
if (buf != NULL)
{
free(buf);
buf = NULL;
}
封装函数
#include<stdio.h>
#include<stdlib.h>
#include<string.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;
for (i = 0; i < n; i++)
{
free(buf[i]);
buf[i] = NULL;
}
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<stdio.h>
#include<stdlib.h>
#include<string.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;
for (i = 0; i < n; i++)
{
free(buf[i]);
buf[i] = NULL;
}
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;
}
分配分析:  释放分析: 
|