一. 回顾
1.1 变量
1.2 内存四区
- 变量三要素里的作用域指的是,它是全局变量还是局部变量。
- 变量的生命周期结合内存四区来看。
1.3 函数调用模型
- 理清程序各个函数运行流程(压栈弹栈,入栈出栈,先进后出)
1.4 指针也是一种数据类型
1.5 void *在使用的时候要转化成它实质的类型
1.6 防止头文件重复包含
#pragma once
这个是防止头文件重复包含,或者交叉重复包含,比如a.h里面展开了b.h,然后b.h里面又展开了a.h,这样就交叉重复包含了,为了防止报错加入#pragma once
,这个时候如果a.h里面展开过b.h,下一次就不会再继续展开了。
1.7 兼容C++编译器
- 这种情况应用于我在.c的文件里封装好的接口函数,想要在.cpp文件里使用,而不用逐个把.c结尾的文件换成.cpp结尾的文件。如下,在主函数里调用socketclient.c里的某个函数,不做兼容C++编译器的处理的时候编译就会报错,所以兼容C++编译器的处理就是为了兼容C代码在C++编译器里运行的时候不用再做修改:
1.8 上一节作业的结构图
二. 指针强化
2.1 指针变量和它指向的内存块的区别
如定义:char *p = "abcde"
- 如果以%s打印表示,把p所指向的内存空间的内容打印出来,操作的是它指向的内存,只是刚好它是字符串
- 如果以%d打印表示,打印的是这个指针变量的值
代码测试:
代码分析,这里p是一个指针变量,我们将它加一之后指向的是字符数组的第二个字符,它本身所指向的内存的内容是没有改变的。
运行结果如下:
结构图如下:
2.2 写内存时,先确保内存可写
<1> 运行以下代码会报错,因为buf存在文字常量区,内存不可修改
char * buf = "asafadfadsgadsga";
buf[2] = '1';
< 2 > 对比之下,如下代码是可以运行,可修改的,因为这里的buf1在栈区开辟了一片连续空间,是把文字常量区里的字符串内容拷贝了一份放在buf1里
char buf1[] = "ssdfsdgsdg";
buf1[1] = 'a';
2.3 小概念
- 指针步长(p++):根据所指内存空间的数据类型来确定
- 不允许向NULL和非法地址拷贝内存
2.4 改变指针指向(对2.1的补充)
这里的流程是现在栈区定义了指针变量q,赋值为NULL,然后在堆区分配了100个字节大小的空间,此时这个空间里面是没有内容的,然后将这个空间的首地址返回给指针变量q保存,运行到strcpy(q,"abcdefg")
时,才将文字常量区里的字符串拷贝到q所指向的堆区空间里。
结构图如下:
自己写的验证代码如下所示:
int main(int argc, char **argv)
{
char* q = NULL;
q = (char*)malloc(100 * sizeof(char));
//判断一下是否动态分配能存成功
if (q == NULL)
{
return -1;
}
strcpy(q, "asdfsadf");
printf("%s\n", q);
}
但是初次写这个代码时犯了如下的错误,这种赋值方式是不对的,错误代码如下:
char* p = (char *)malloc(100 * sizeof(char));
*p = "adsadfa"; //字符串这样赋值可不行,必须得用strcpy的形式
printf("%s\n", p);
但是以下代码又是正确的,画一个内存四区图就能理解,这里虽然开辟了一段堆区内存,但是没有用它,本来p是指向堆区内存的,但是我下面的给指针变量赋值又把存在于文字常量区的字符串的首地址给了p,现在的p是指向文字常量区的这段字符串的,代码如下:
char* p = (char *)malloc(100 * sizeof(char));
p = "adsadfa";
printf("%s\n", p);
2.5 指针做参数的输入输出特性
< 1 > 输入
主调函数分配内存,此处的buf作为函数的参数,主调函数已经给buf分配过内存了
< 2 > 输出
被调函数分配内存,需要传递地址
void fun3(char **p,int *len)
{
if(p == NULL)
{
return; //这里return什么都不加表示退出函数,如果满足p == NULL就退出函数
}
char * tmp = (char *)malloc(100);
if(tmp == NULL) //验证有没有成功在堆区分配到内存,如果没有分配到返回的是NULL,也退出函数
{
return;
}
strcpy(tmp, "agdsgdsg");
//间接赋值
*p = tmp;
*len = strlen(tmp);
}
int main(void)
{
char *p = NULL;
int len = 0;
fun3(&p, &len);
if(p != NULL)
{
printf("p = %s, len = %d\n",p,len);
}
}
结构图如下:
三. 字符串
3.1 字符串初始化
- C语言没有字符串类型,通过字符数组模拟
- C语言字符串,以字符’\0’,数字0
- 字符0是一个字符,有对应的ASCII码,它和数字0以及’\0’不同
sizeof()
和strlen()
- 为了确保定义的数组最后内存会被释放,所以数组名是一个常量,因为它是一个常量,且定义了数组之后大小是确定的,根据这个常量以及数组的大小,系统知道结束后释放多大的内存。
3.2 字符串拷贝
< 1 > 未封装成函数
以下为课上较标准的方式写的:
以下是我自己写的实现字符串拷贝函数,贼特么笨,在这里我刚开始初始化目的字符串的这种方式char dst[] = { 0 };
是不可取的,因为这样赋值的话,这个数组的大小是只能容纳一个元素,在后面就会出现数组越界的情况,不要这么干。同时这里的目的字符串初始化char dst[100];
没有初始化值是因为如果初始化为0,最后面就不想要我们自己在目的字符串里补结束符0,我们这里是为了学习,提醒自己后面要补上一个结束符:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc, char** argv)
{
char src[] = {
"asdfasdfa" };
//char dst[] = { 0 }; //目的字符串这样初始化可不行
char dst[100];
char* p = NULL;
p = src;
int i = 0;
int n = 0;
while (*p != '\0')
{
dst[i] = *p;
i++;
p++;
}
n = strlen(src);
dst[n] = '\0';
printf("%s", dst);
printf("\n");
return 0;
}
< 2 > 封装成函数
- 形式1,操作下标
- 形式2,操作指针
- 形式3,比形式2更高级,别这么写,有点难看懂的
- 形式4,更骚的版本,别这么写,有点难看懂的
- 形式5,行业标杆的写法
- 加入了判断位,防止形参指针输入为NULL而直接造成程序报错
- 最好不要直接使用形参,对其修改等操作,找个变量存取它,此处定义了
char *to = dst
和char *from = src
保证了传进来的形参不会被改变
// 成功为0,失败非0
int my_strcpy4(char *dst, char *src)
{
if(dst == NULL || src == NULL)
{
return -1;
}
//辅助变量把形参接过来
char *to = dst;
char *from = src;
// *dst = *src
// dst++, src++
//判断 *dst是否为0,为0跳出循环
while(*to++ = *from++)
{
NULL;
}
//此时若打印dst,因为地址已经指向'\0'了,打印不出来任何东西,所以传参的时候不要直接使用形参
//printf("my_strcpy4: dst = %s\n",dst)
return 0;
}
int main(void)
{
char src[] = "asadsfafsad";
char dst[100] = {
0 };
int ret = 0;
ret = my_strcpy4(dst, src);
//此时传给函数NULL,程序不会崩
if(ret != 0)
{
printf("my_strcpy4 err:%d\n", ret);
return ret; // 能够进入这里表示,此时程序已经报错了,这里return让程序结束掉,不会再运行下面的打印字符串的程序了
}
printf("%s\n",dst);
return 0;
}
3.3 strstr中的while和do while模型
< 1 > do-while
int main(void)
{
char *p = "11abcd111122abcd3333322abcd3333322qqq";
int n = 0;
do
{
p = strstr(p,"abcd");
if(p != NULL)
{
n++; //累计个数
//重新设置查找的起点
p = p + strlen("abcd");
}
else //如果没有匹配的字符串,跳出循环
{
break;
}
}while(*p != 0); //如果没有到结尾
printf("n = %d\n", n);
return 0;
}
< 2 > while
int main(void)
{
char *p = "11abcd111122abcd3333322abcd3333322qqq";
int n = 0;
while((p = strstr(p, "abcd")) != NULL)
{
//能进来,肯定有匹配的子串
//重新设置起点位置
p = p + strlen("abcd");
n++;
if(*p == 0) //如果到结束符
{
break;
}
}
printf("n = %d\n", n);
return 0;
}
< 3 > 封装函数
int my_strstr(char *p,int *n)
{
//辅助变量
int i = 0;
char *tmp = p;
while((tmp = strstr(tmp, "abcd")) != NULL)
{
//能进来,肯定有匹配的子串
//重新设置起点位置
tmp = tmp + strlen("abcd");
i++;
if(*tmp == 0) //如果到结束符
{
break;
}
}
//间接复制
*n = i;
return 0;
}
int main(void)
{
char *p = "11abcd111122abcd3333322abcd3333322qqq";
int n = 0;
int ret = 0;
//通过形参修改实参,需要传地址
ret = my_strstr(p, &n);
if(ret != 0)
{
return ret;
}
printf("n = %d",n);
return 0;
}
3.4 两头堵模型
< 1 > 未封装函数型
< 2 > 封装函数型
因为这里没有改变首地址,所以写进函数的时候可以不用找个变量存取字符串的首地址,记得与上面的区分开。
int fun(char *p, int *n)
{
if(p = NULL || n == NULL)
{
return -1;
}
int begin = 0;
int end = strlen(p) - 1;
//从左边开始
//如果当前字符为空,而且没有结束
while(p[begin] == ' ' && p[begin] != 0)
{
begin++; //位置从右移动一位
}
//如果当前字符为空,而且没有结束
while(p[end] == ' ' && p[end] != 0)
{
edn--; //往左移动一位
}
*n = end - begin + 1;
return 0;
}
int main(void)
{
char *p = " abcdefg ";
int ret = 0;
int n = 0;
ret = fun(p, &n);
if(ret != 0)
{
return ret;
}
printf("n = %d\n", n);
return 0;
}
3.5 两头堵模型强化
int fun(char *p, int *n)
{
if(p = NULL || n == NULL)
{
return -1;
}
int begin = 0;
int end = strlen(p) - 1;
int n = 0;
//从左边开始
//如果当前字符为空,而且没有结束
while(p[begin] == ' ' && p[begin] != 0)
{
begin++; //位置从右移动一位
}
//如果当前字符为空,而且没有结束
while(p[end] == ' ' && p[end] != 0)
{
edn--; //往左移动一位
}
n = end - begin + 1;
strncpy(buf, p + begin, n);
buf[n] = 0;
return 0;
}
int main(void)
{
char *p = " abcdefg ";
int ret = 0;
char buf[100] = {
0};
ret = fun2(p, buf);
if(ret != 0)
{
return ret;
}
printf("buf = %s",buf);
return 0;
}
四. 思考题
今天的文章C enhance Day(二)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/84096.html