c语言学习记录
Unix中apue.h中err_doit函数分析
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf[MAXLINE];
vsnprintf(buf, MAXLINE, fmt, ap);
if (errnoflag)
snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s",strerror(error));
strcat(buf, " ");
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(NULL); /* flushes all stdio output streams */
}
一开始阅读这个代码感觉很懵,为什么在头文件中定义static 函数,vsnprintf是什么,snprintf又是什么?
头文件中的static函数
在头文件中定义static函数,当其他文件引入头文件的时候,相当于其他文件在自己的源文件中定义了static函数,因为头文件的引入本质上就是将引入的头文件搬过去。
vsnprintf
头文件:
#include <stdio.h>
函数声明:
int _vsnprintf(char* str, size_t size, const char* format, va_list ap);
参数说明
- char *str [out],把生成的格式化的字符串存放在这里.
- size_t size [in], str可接受的最大字符数 [1] (非字节数,UNICODE一个字符两个字节),防止产生数组越界.
- const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
- va_list ap [in], va_list变量. va:variable-argument:可变参数
功能
这个函数可以将不同形式的输入参数整合到一个字符串中(不知道是我理解力不行还是怎么回事,网上的感觉太晦涩,看不懂,啊,原来是机翻,吐了,链接在这https://cplusplus.com/reference/cstdio/vsnprintf/)
Composes a string with the same text that would be printed if format was used on printf, but using the elements in the variable argument list identified by arg instead of additional function arguments and storing the resulting content as a C string in the buffer pointed by s (taking n as the maximum buffer capacity to fill).
snprintf
这个函数和上面的函数搭配使用,第一个是为了整合包字符串,这个我觉得是为了更好的防止内容越界,操作更加方便。
int snprintf ( char * s, size_t n, const char * format, ... );
snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s",strerror(error));
这里始终让写入的指针指向下一个空的数组,然后再进行写入,同时规定了写入的大小的最大值为数组剩余的大小,这样多的便会丢弃,也不会报错。
关于fmt,我理解的就是类似于printf那样,咱们在打印的时候必须跟着数据类型和数据printf("hello zhangsan %d\n,num");
这样num和%d,就是一种带format的输入数据。
关于ap,这是一种可变参数列表类型,听名字就知道了,是一堆类型不一样的数据组成的列表。我先只列出用法,以后学习了再补充。
例如:
void
err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
exit(1);
}
函数的入口参数是一个char * fmt,那么咱们用的时候就传入一个字符串就可以了嘛,后边至于再传入什么,再按format 格式传入就好了。
然后声明一个ap
然后在用start初始化等等,都是一套流程,这里原本一般是
#include <stdarg.h>
#include <stdio.h>
#include "Stdafx.h"
void test(const char * fmt, ...)
{
char buf[256];
int num = 0;
va_list ap;
va_start(ap,fmt);
num = _vsnprintf(buf,255,fmt,ap);
printf ("%s",buf);
va_end(ap);
}
int main ()
{
test("%s,%d,%d,%c","abc",1,22,'\n');
return 0;
}
这样就可以打印各种不同类型的数据
abc,1,22,
Press any key to continue
指针数组数组指针
头疼的问题,看了N遍记不住
感觉b站这个视频讲的很好,下面代码也是视频中的代码
指针数组,ennn,就是一堆数组,但是里面存放的是指针。
arr1[5]={0};
arr2[5]={0};
arr3[5]={0};
char * arr[3] = {arr1,arr2,arr3};
在这里arr就是指针数组,可以先从符号结合的优先级看,中括号[ ]的优先级是高于*指针运算符的。所以上面的式子首先是一个数组,然后才是一个指针。所以称之为指针数组,里面存放的是每个小数组的地址,如果想打印每个元素的话可以这样打印。
for (int i = 0; i < 3; i++)
{
for (int j = 0;j<5;j++)
{
printf("%c",*(arr[i]+j));
}
}
因为首先的话,其实跟数组没什么区别,就直接把int *看成是数据类型就可以了,然后后面就是普通的数组。数组的第一个代表的就是arr1的首地址,然后再加上对应的数值,就是此数组向后偏移的数据的地址,然后再加上指针解引用符号求出来的就是真实的数据的值。
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = &arr;
上面的将数组的地址复制给指针p是不对的,因为p是一个整形指针,而一个整形指针是无法存放一个数组地址的(这里的数组地址说的其实是整个大数组的地址,是由许多小元素组合起来的那个块)
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int * p[10] = &arr;
这样写也是不对的,因为这就相当于我们之前说的这是一个指针数组,按结合的优先级来说,中括号会先于p结合,那么p都不是指针,怎么可能代表数组的地址呢。int *相当于一个数据类型。
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;
而这个样子是可以的,因为这样p表示的不是整个数组,而是表示的是数组的第一个元素的地址,也就是数组的首地址。
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;
这样表示是可以的,以为根据结合的优先级来说,首先p是一个指针,指向什么呢?指向的是一个10个空间大小的int数据,而等号右边也刚好是一个指向10个空间大小的指针,所以两边才能相等。
下面的这个例子更能加深对数组指针的理解
printf("%d ",*(*(p+i)+j));
首先要知道,二维数组传进来的数组名代表的是什么。二维数组数组名代表的是一维数组的地址,而不是一维数组第一个元素的地址,这就又回到了一开始函数参数那里,也就是我函数的入口参数接收就要首先声明成一个指针,指针指向的是这个一维数组,然后要指针类型一样,指向的是输入的一维数组大小一样的空间。
那么这里的p也就是二位数组的第一个数组,然后p+i指向的是第i个数组取地址后代表的就是第i个数组的首元素的地址,然后再+j就是第i个数组的第j个元素的地址,然后再取地址,得到的就是i行j列的元素了。
后面的几种也可以这么理解p[j][i],相当于直接去数组中取值,第一个取的是某一行,第二个是取的那一行的值。
(*(p+i))[j] 这就相当于先按第一种思考方式,取出第i行的首元素的地址也就是相当于一个一维数组名,然后再用[j],求出的就是某一行第j个元素的值。
#include <stdio.h>
void print1(int arr[3][5],int x,int y)
{
for (int i=0;i<x;i++)
{
for (int j=0;j<y;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
void print2(int (*p)[5] ,int x,int y)
{
for (int i=0;i<x;i++)
{
for (int j=0;j<y;j++)
{
//printf("%d ",*(*(p+i)+j));
//printf("%d ",(*(p+i))[j]);
//printf("%d ",p[i][j]);
printf("%d ",*(p[i]+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
//print1(arr,3,5);
print2(arr,3,5);
return 0;
}
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理