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);

参数说明

  1. char *str [out],把生成的格式化的字符串存放在这里.
  2. size_t size [in], str可接受的最大字符数 [1] (非字节数,UNICODE一个字符两个字节),防止产生数组越界.
  3. const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
  4. 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站这个视频讲的很好,下面代码也是视频中的代码

https://www.bilibili.com/video/BV1ah411R7dp?p=4&share_source=copy_web&vd_source=7821c4dcf9cb0abad327892a7b18d02b

指针数组,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;
}

eg: int (* parr[10])[5]

这是个什么东东呢?首先小括号里面[10]先与parr结合,形成一个数组,然后在跟*结合,形成一个包含10个指针数据的数组,那么这就是一个数组指针。这些数组指针里每一个指针存放的数据是什么呢?每一个数据是后面的int类型的含有5个元素的那个数组的地址,相信说到这里大家就能明白了。

二维数组传参只能省略行不能省略列,而且不可以全部省略,一维数组可以。

老师又给了两道例题是分别是

*(void(*)())0)();

void(*signal(int,void(*)(int)))(int);

  1. 首先看第一个,第一个先从熟悉的地方看起,也就是0的地方,然后在向前面看(void(*)())这是个函数指针啊,多么熟悉,在函数指针外面又加了一个小括号,那不就是强制转换吗?把什么强制转换成函数指针呢?当然是后面的0,也就是说,0是一个函数指针类型,那么这个0处存放的就是某一个函数的地址,然后在将这个函数指针类型解引用,求得的就是0处存放的对应的函数的地址,然后又组成了一个函数。

    老师解释的是将0强制转换成函数指针类型,然后去调用这个0地址处的函数参数类型为空的这个函数,总的来说就是一次函数调用。

  2. 首先这是一个函数声明,signal是一个函数名,函数的第一个参数为整型,第二个参数为函数指针,然后函数的返回类型为void(*)(int),返回的也是一个函数指针。

    老师说可以这样定义,使用起来更方便也就是使用typedef

    typedef void(* pfun_t)(int);
    pfun_t signal(int pfun_t);
    

    这两个是等价的。

优先级

下面的语句,我们的本意可能是将hi左移四位加上low四位,构成一个8位的数据。但是这可能会得到意向不到的结果,因为在这里 " + " 的优先级是高于 “<<” 运算符的所以会在4+low的基础上左移hi个长度。

r  = hi<<4 + low ;

上条语句的效果其实是这样的

r  = hi<< (4 + low) ;

解决的方法有两种

r = (hi<<4) + low;

r = hi<<4 | low;

数组中两个元素地址相减

https://blog.csdn.net/anycell/article/details/7271842

int arrayTmp[10] = {0};
int nTmp = &arrayTmp[4]-&arrayTmp[0];
int nTmp = &arrayTmp[4] - &arrayTmp[0];
00416B87  lea         eax,[ebp-28h] 
00416B8A  lea         ecx,[arrayTmp] 
00416B8D  sub         eax,ecx 
00416B8F  sar         eax,2 
00416B92  mov         dword ptr [nTmp],eax 

两个数组元素地址相减,实际是获取两个元素数组元素的距离,而不是地址的距离。

const修饰

指针本身是常量不可变。指针自身的值是一个常量,指向的地址不可以改变,始终指向同一个地址

巧记:只要是后面的变量先和指针结合的那么就是指针指向的内容不能变。

const (char *)pContent
char * const pContent

指针指向的内容不可变

const char * pContent
char const * pContent
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值