C语言_常见知识点

一、关于printf函数

1.printf函数是有返回值的,printf返回输出字符的个数。

#include<stdio.h>
int main()
{
	int i = 43;
	printf("%d\n", printf("%d",printf("%d",i)));//printf函数返回值为输出字符的个数//4321
	return 0;
}

2.下面这题来自牛客网,考察知识点为函数参数传递方式,但是因为用到了printf函数,就放到这里一并说。

求下列程序的输出结果

#include<stdio.h>

int f(int a, int b, int c)
{
	return 0;
}

int main()
{
	return f(printf("a"), printf("b"), printf("c"));
	return 0;
}

未定义行为。

二、智力题

1.寝室有6个同学打dota,分为对立的两方,一方是天灾军团,一方是近卫军团。现请你设置赛程以及每场的对阵(每方最少1人、最多5人),请问至少得进行多少场比赛,才能使得赛程结束后每位同学都和其他同学做过对手(3)

解答:这题当时做对了,是试出来的。看了一下网友的解答,感觉很不错。

用0,1来表示每个同学位于哪一方,0表示天灾军团,1表示近卫军团。000表示这个同学3次都处于天灾军团,001表示两次处于天灾,一次处于近卫。000,001,010,011,100,101,110,111,八个二进制最少有一个数不同,表示处于对抗。这题相当于几位二进制,可以表示5个不同的数。

2.从一副标准扑克牌中抽牌,抽到黑色牌就继续抽(不取出),直至抽到红色牌,则停止。按照概率算,平均下来每次能抽到多少张黑牌?(也就是三国杀中甄姬的洛神技能,得到牌数的期望值)

 

1
1.2
0.8
0.9

解答:做的时候,2了,没有看到抽到的黑色牌继续放回牌堆。其实这题很简单,只不过我也玩三国杀,觉得很有意思就放在这里了。

E=1/4+2/8+3/16+4/32+...+n/(2^n+1)

=1

三、C语言中结构体占用内存问题

转载:https://www.cnblogs.com/kl2blog/p/6908048.html

之前对结构体占用内存一直很混乱,到底是按照哪个变量类型计算内存?还是怎么计算?下面先看一个例子:

struct str1
{
	char a;
	int b;
	float c;
	double d;
};

str1这个结构体占用的内存是多少呢?如果用变量类型直接想加,得到的结果是17,但显然不是这样的。这个程序运行的正确结果是24.为什么呢?

  因为为了CPU能够快速访问,提高访问效率,变量的起始地址应该具有某些特性,这就是所谓的“对齐”。比如4字节的int型变量,那它的起始地址就应该在4字节的边界上,即起始地址可以被4整除。

  内存对齐的规则很简单:

  1.起始地址为该变量类型所占内存的整数倍,若不足则不足部分用数据填充至所占内存的整数倍。

  2.该结构体所占总内存为结构体成员变量中最大数据类型的整数倍。

  接下来我们分析上面的例子:

  char型变量占一个字节,所以它的起始地址为0,而int类型占4个字节,它的起始地址应该是4(的整数倍),那么内存地址1、2、3就需要被填充。同样,float占用4个字节,而结构体中a,b两个成员变量占了0~7内存地址,c的地址从8开始,符合规则一,占用内存地址为8~11。double类型占8个字节,所以d的起始地址就应该从16开始,那么12、13、14、15内存地址就需要被填充。d从16地址开始,占用8个字节。整个结构体占用字节数为24,符合规则二。内存分配如图:红色区域为填充部分

 

下面再举一个例子,进一步说明:

struct str2
    {
        double a;
        int b;
        char c;
        double d;
    };

str2这个结构体占用的内存空间是多少呢?是24!怎么分析呢?

首先double类型的a占用内存地址为0~7,int类型的b起始地址为8,符合规则一,占用地址为8~11,char类型的c占一个字节,地址为12.那么double类型的d,起始地址为13吗?显然不是,满足规则一的地址是16,所以d起始地址为16,占用16~23。结构体总共24个字节,满足规则二。如果这个结构体最后再加一个成员变量 char e,那这个结构体占用的内存是多少?char类型的e起始地址为24,占用地址为24,但是结构体一种有25个字节,就不满足规则二了,怎么办呢?为了满足规则二,我们将25~31进行填充,因此整个结构体占用32个字节。

#include<stdio.h>

struct str2
{
	double a;
	int b;
	char c;
	double d;
	char e;
};

int main(void)
{
	printf("%d\n", sizeof(struct str2));
	return 0;
}

 

下面再看一个牛客网上例子:

以下代码打印的结果是(假设运行在 64 位计算机上):

struct st_t {
    int status;
    short *pdata;
    char errstr[32];
};
st_t st[16];
char *p=(char *)(st[2].esstr+32);
printf(“%d”,(p-(char *)(st)));

status占4个字节,字节补齐4个字节,

64位系统,指针占8个字节,pdata占8个字节

errster占32个字节

整个结构体占48个字节。

最后答案为144.

结构体成员重排:

如果原来结构体成员是这样定义的

 

struct str2
{
	char a;
	double b;
	int c;
};

那它将占用24个字节内容;

我们可以按照变量类型大小来定义变量,把占用内存大的变量定义在前面

struct str2
{
	double b;
	int c;
	char a;
};

那它将占用16个字节。

四、C语言函数参数的计算顺序与入栈顺序

先计算,后入栈

计算顺序:

例如printf("%d,%d\n",++i,--i);

在C语言中并没有规定参数的执行顺序,不同编译器有不同的实现,到底先执行++i还是先执行--i是不定的。

例如fun(fun1(),fun2()),我也不知道fun1,fun2谁先执行

入栈顺序:

x86下,函数入栈一般是从右至左入栈:

x86_64下,函数前6个整型参数,放到寄存其中,其他参数从右至左入栈

函数调用约定决定了参数的入栈顺序,C语言默认的是__cdecl方式,C Declaration,它是从右至左的。

#include <stdio.h>

int test(int a, int b)
{
	printf("address of a %x.\n", &a);//42fbec
	printf("address of b %x.\n", &b);//42fbf0,b先入栈
	return 0;
}

int main(void)
{
	test(1, 2);
	return 0;
}

五、如何比较两个浮点数是否相等。

我们知道浮点数必能精确存储,在比较浮点数知否相等的时候,我们一般不直接使用==来比较,而是通过预先设置一个精度来比较,比如0.000001,当fbs(a-b)<0.00001时,就认为两个浮点数相等。

例如:

#include <stdio.h>  

int main(int argc, char *argv[])
{
	double a = 2.7 + 2.6;
	double b = 5.3;
	
	if (a == b)
	{
		printf("a==b\n");
	}
	else
	{
		printf("a!=b\n");
	}

	return 0;
}

 

#include <stdio.h>  
#include <math.h>

int main(int argc, char *argv[])
{
	double a = 2.7 + 2.6;
	double b = 5.3;
	printf("%.32f\n", a - b);
	if (fabs(a - b) < 0.000001)
	{
		printf("a==b\n");
	}
	else
	{
		printf("a!=b\n");
	}
	return 0;
}

abs和fabs区别:

abs:对整数求绝对值

fabs:对浮点数求绝对值

函数原型:

原型:int abs(int j);
原型:double fabs(double x);

六、任意给一个点P,和四边形四个顶点的坐标,判断点P是否在四边形内部?

分凸四边形和凹四边形,这里只说一下凸四边形的

方法一:根据向量的叉乘(外积)

假设四边形的四个顶点为A,B,C,D,如图所示,如果点P在四边形内部,则有

ABxAP、BCxBP、CDxCP、DAxDP符号相同,要么都向上,要么都向下。

向量叉乘公式:m x n = |m|x|n|sin(\theta)

用坐标表示时,AB=(B.x-A.x,B.y-A.y),AP=(P.x-A.x,P.y-A.y)

ABxAP=(B.x-A.x)*(P.y-A.y)-(B.y-A.y)*(P.x-A.x)

#include<stdio.h>

struct point
{
	int x, y;
};

int main(void)
{
	struct point A = { 1,0 }, B = { 3,0 }, C = { 4,3 }, D = { 2,3 }, P = { 2,2 };
	int a = (B.x - A.x)*(P.y - A.y) - (P.x - A.x)*(B.y - A.y);
	int b = (C.x - B.x)*(P.y - B.y) - (P.x - B.x)*(C.y - B.y);
	int c = (D.x - C.x)*(P.y - C.y) - (P.x - C.x)*(D.y - C.y);
	int d = (A.x - D.x)*(P.y - D.y) - (P.x - D.x)*(A.y - D.y);
	if ((a > 0 && b > 0 && c > 0 && d > 0) || (a < 0 && b < 0 && c < 0 && d < 0))
	{
		printf("点P在四边形内部\n");
	}
	else
	{
		printf("点P不在四边形内部\n");
	}
	return 0;
}

 方法二:

如果一个点在某个四边形内部,可以通过这个点将四边形或分为三个三角形,则四边形的面积和为这四个三角形面积的和

 

#include<stdio.h>
#include<math.h>

struct point
{
	int x, y;
};

//已知三角形三个顶点的坐标,求三角形的面积,就是叉乘嘛
float TriangleSquare(struct point A, struct point B, struct point C)
{
	return fabs((B.x - A.x)*(C.y - A.y) - (B.y - A.y)*(C.x - A.x)) / 2.0;
}

int main(void)
{
	struct point A = { 1,0 }, B = { 3,0 }, C = { 4,3 }, D = { 2,3 }, P = { 2,2 };
	float square1 = TriangleSquare(A, B, D) + TriangleSquare(B, C, D);
	float square2 = TriangleSquare(A, B, P) + TriangleSquare(B, C, P) + TriangleSquare(C, D, P) + TriangleSquare(A, D, P);
	printf("square1,square2:%f,%f\n", square1, square2);
	if (fabs(square1-square2)<0.00001)
	{
		printf("点P在四边形内部\n");
	}
	else
	{
		printf("点P不在四边形内部\n");
	}
	return 0;
}

七、如何避免头文件被重复包含

使用条件编译:

我们在写程序的时候,例如用到了a.h和b.h头文件,a.h和b.h头文件中又都包含了c.h头文件,这样就可能导致重复引用头文件c.h。为了避免这种情况发生,我们在定义头文件的时候,一般都使用条件编译;例如,现在需要定义一个头文件my.h。可以这样定义:

#ifndef _MY_H

#define _MY_H

...

#endif

这样我们在我们的源程序中如果已经包含了一个my.h头文件,这样在第二次试图包含my.h的头文件的时候,就不会包含了。

八、写一个函数式宏,实现这个宏输入两个参数,返回较小的一个

#define MIN(x,y) ((x)<=(y)?(x):(y))

注意,这里(x)和(y)都需要加括号,

例如x为0&&1;y为2&&3;正常结果应该返回0;如果x,y不加括号的话,MIN(x,y)就是替换为(0&&1<=2&&3?0&&1:2&&3),这样结果就会为1,错误。

MIN(x,y)直接不要有空格,不要写为MIN (x,y),这样编译器会把MIN 当成(x,y) ((x)<=(y)?(x):(y))处理。

九、、#include<file.h> 与 #include "file.h"的区别?

前者从标准库文件路径搜索file.h,后者从当前工作路径搜索file.h。

十、函数指针和指针函数的用途

函数指针:主要用做回调函数,就是函数指针作为参数传递给另外的函数,在其他函数中可以调用该函数。更加通用。例如根据传来的参数实现加或者减运算,不用函数指针的话就需要判断。

指针函数:像字符串复制,字符串连接都有使用。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页