C语言经典笔试题--整形提升、大小端、前置与后置++、二进制运算规则(1与0的问题)、越界访问问题

目录

前言

1.笔试题

1.1 ++a与a++

1.2 二进制0和1

1)统计二进制中1的个数

2)求两个数的二进制中不同位的个数

 1.3 判断元音辅音字母

1.4 以下代码在VS环境下运行结果是?

 1.5 模拟实现strcpy

1.6 1-1在计算机中的计算

1.7 大小端字节序

方法一:

 方法二:

1.8 整形提升

1.8.1 整形提升的陷阱

 1.8.2 无符号整形打印

1.8.3 补码相加原则

1.8.4 死循环--无符号的陷阱

1.8.5 无符号字符型数组的长度

2.结构体



前言

C语言部分基础知识的回顾和一些笔试题

1.笔试题

1.1 ++a与a++

        a++是首先拷贝自己的副本,然后对真值加一
        ++a是对真值加一,然后使用真值

        c语言提供一种特殊的运算符,逗号运算符,优先级别最低,它将两个及其以上的式子联接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值。如:(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值,如:(3+5,6+8)的值是14;a=(a=3×5,a×4)的值是60,其中(a=3×5,a×4)的值是60, a的值在逗号表达式里一直是15,最后被逗号表达式赋值为60,a的值最终为60。

逗号表达式详解_x=(x++,y++)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_43539854/article/details/105757536

#include<stdio.h>
int main()
{
	int a = 0;
	int c = 0;
	int b = 0;
	a = 5;
	c = ++a;//c = 6
	b = ++c, c++, ++a, a++;//b = 7,c = 8,a = 8// b = ++c是一个整体,注意区分逗号表达式
	b += a++ + c;//b = 8+8+7 = 23
	//b += ++a + c;//b=9+8+7 = 24
	printf("a = %d,b = %d,c = %d\n", a, b, c);
	return 0;
}

注意:b += a++ + c;先计算b = a+c+b再计算a+1

注意:这样就是执行了逗号表达式:

#include<stdio.h>
int main()
{
	int a = 0;
	int c = 0;
	int b = 0;
	a = 5;
	c = ++a;
	b = (c, c++, ++a, a++);
	

	printf("a = %d,b = %d,c = %d\n", a, b, c);
	return 0;
}

 这样b就等于最后表达式的值a,然后a再自增,所以得到如下结果:

                                                                                a = 8,b = 7,c = 7

去掉括号后,c就会直接被赋值为c,而本来c=6 

1.2 二进制0和1

1)统计二进制中1的个数


注意://1000 0000 0000 0000 0000 0000 0000 0001 - 补码
//1111 1111 1111 1111 1111 1111 1111 1110 - 反码
//1111 1111 1111 1111 1111 1111 1111 1111 - 原码
//无符号会被认为是上述1的原码没有符号位

int count_number_1(unsigned int n)//这样写,那我就认为我传过来的是无符号位的
1111 1111 1111 1111 1111 1111 1111 1111
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if (((n >> i) & 1 ) == 1)
		{
			count++;
		}
	}
	return count;
}	

异或的方法:相同为0,不同为1

m = m&(m-1)可以得到bit位中1的个数

//001111m
//001110m-1
//001110m'
//001101m'-1
//001100m''
//001011m''-1
//001000m'''
//000111m'''-1
//000000m''''
//进行了4次m&(m-1),4个1

求一个数二进制数中1的个数的三种方法_怎么求一个数字每个二进制上有几个1-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_35890909/article/details/134233505?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-134233505-blog-135833091.235%5Ev43%5Epc_blog_bottom_relevance_base1&spm=1001.2101.3001.4242.1&utm_relevant_index=3

2)求两个数的二进制中不同位的个数

VS常见错误之一:LNK1168无法打开进行写入-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/ymxyld/article/details/125052464

//异或,相同为0,相异为1
#include<stdio.h>
count_difbit(int a, int b)
{
	int count = 0;
	int temp = a ^ b;//不同的bit位都是1,找1的个数即可
	while(temp)
	{
		temp = temp & (temp - 1);
		count++;
	}
	return count;
}
int main()
{
	int m = 1999;
	int n = 2299;
	int ret = count_difbit(m, n);
	printf("%d\n", ret);
}
//15 7
//00000000 00000000 00001111 -15
//正数原码补码反码相同,则15与7不同个数为1
//00000000 00000000 00000111 -7
#include<stdio.h>
int count_difbit(int a, int b)
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if ((a & 1) != (b & 1))
		{
			count++;
		}
		a >>= 1;
		b >>= 1;
	}
	return count;
}
//-5
//10000000 00000000 00000101
//11111111 11111111 11111010
//11111111 11111111 11111011
//-1
//10000000 00000000 00000001
//11111111 11111111 11111110
//11111111 11111111 11111111
int main()
{
	int m = 0;
	int n = 0;
	scanf("%d %d", &m, &n);
	int ret = 0;
	ret = count_difbit(m,n);
	printf("%d\n", ret);
	return 0;

}

 1.3 判断元音辅音字母

getchar();的作用:

从标准输入流只读取一个字符(包括空格、回车、tab),读到回车符('\n')时退出,
键盘输入的字符都存到缓冲区内,一旦键入回车,getchar就进入缓冲区读取字符,
一次只返回第一个字符作为getchar函数的值,
如果有循环或足够多的getchar语句,就会依次读出缓冲区内的所有字符直到'\n'.

要理解这一点,之所以你输入的一系列字符被依次读出来,
是因为循环的作用使得反复利用getchar在缓冲区里读取字符,
而不是getchar可以读取多个字符,事实上getchar每次只能读取一个字符.

如果需要取消'\n'的影响,可以用getchar()来清除,
如:while((c=getchar())!='\n'),这里getchar();只是取得了'\n'
但是并没有赋给任何字符变量,所以不会有影响,相当于清除了这个字符。
#include<stdio.h>
int main()
{
	char v[] = { 'A','a','E','e','I','i','O','o','U','u' };
	char ch = 0;
	while (EOF != scanf("%c", &ch))
	{
		getchar();
		int i = 0;
		for(i = 0;i<10;i++)
		{
			if (ch == v[i])
			{
				printf("vov\n");
				break;
			}
			
		}
		if (i == 10)
		{
			printf("consonat\n");
		}
	}
	return 0;
}

注意:这样会错误,如果不用getchar

1.4 以下代码在VS环境下运行结果是?

    i和arr是局部变量,放在栈区上,栈区上内存的使用习惯是先使用高地址处的空间,再使用低地址处的空间,(因为栈的特点是后进先出)

        数组随着下标的增长,地址是由低到高变化,如果i和arr之间的空间适合的话,就有可能使用的arr数组向后越界就访问到i造成循环变量i的改变,最终死循环

&i:栈区上内存的使用习惯是先使用高地址处的空间,再使用低地址处的空间,i在高地址

从下图可以看出arr的地址和i的地址。

&arr:数组随着下标的增长,地址是由低到高变化

当i>9之后就越界了,出现了arr[10]。

 当i = 12,i++之后i变成了0,因为刚好覆盖了!

 

  vcc6.0 - i和arr是连续的
    gcc - i和arr之间有一个空间

    如果将debug变成delease不会报错,他会让i到低地址去,但是这种优化是无差别的

#include<stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//for (i = 0; i <= 11; i++)//越界
	for (i = 0; i <= 12; i++)//死循环
	{
		arr[i] = 0;//ctrl+f自动对齐操作快捷键
		printf("hehe\n");
	}
	return 0;
}

 1.5 模拟实现strcpy

注意:如何让代码更美观?参考以下代码:

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dst,const char* src)
{
	assert(dst && src);
	char* ret = dst;
	while (*dst++ = *src++);
	//当src++到\0后,赋值给dst,解引用得到\0,ascii码值就是0,跳出循环
	return ret;
}
int main()
{
	char arr1[20] = {0};
	char arr2[] = "hello";
	printf("%s\n", my_strcpy(arr1, arr2));
	return 0;
}

1.6 1-1在计算机中的计算


1.内存中以补码形式存放 2.只有加法1+(-1)3.把最高位丢了

0000 0000 0000 0000 0000 0000 0000 0001 - 正数的补码反码原码一样 - 1的补码
1000 0000 0000 0000 0000 0000 0000 0001 - -1的原码
1111 1111 1111 1111 1111 1111 1111 1110
1111 1111 1111 1111 1111 1111 1111 1111 - -1的补码
0000 0000 0000 0000 0000 0000 0000 0001 - 1的补码
10000 0000 0000 0000 0000 0000 0000 0000 - 1+(-1),超出了32位,规定超出位丢弃
0000 0000 0000 0000 0000 0000 0000 0000 - 0的补码反码原码一致

1.7 大小端字节序

小端模式优点

内存的低地址处存放低字节,所以在强制转换数据时不需要调整字节的内容(注解:比如把int的4字节强制转换成short的2字节时,就直接把int数据存储的前两个字节给short就行,因为其前两个字节刚好就是最低的两个字节,符合转换逻辑);

大端模式优点:符号位在所表示的数据的内存的第一个字节中,便于快速判断数据的正负和大小

方法一:

        以下方法是常见的错误方法,因为这样,已经变化了原本的a,从int变成了char,发生了类型转换。

#include<stdio.h>
int check_smallorbig(int n)
{
	char m = (char)n;
	return m;
}
int main()
{
	int a = 1;//0x 00 00 00 01
	//01是低字节,低字节存放在低地址为小端
	int ret = check_smallorbig(a);
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

        &a是int*类型,但是这样得到的是4个字节,但是我只想得到一个字节,故强制类型转换成char*,p还是指向的a,p存放的还是a的地址,只是访问的是第一个字节,所以应该用如下的方法才正确。

#include<stdio.h>
int main()
{
	int a = 1;
	char* p = (char*)&a;//return *((char*)&a);
	
	if (*p == 1)
	{
		printf("xiaoduan\n");
	}
	else
	{
		printf("shabi\n");
	}
	return 0;
}

 

 方法二:

        结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

        结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

先看一段代码:

https://blog.csdn.net/m0_57180439/article/details/120417270

#include<stdio.h>
union Un//联合类型的声明,union是联合体关键字
{
	char c;//1字节
	int i;//4字节
};
int main()
{

	union Un u = {0};
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.c));//u.c表示联合体的成员c,该引用方法类似结构体
	printf("%p\n", &(u.i));
}

         由sizeof(u)我们知道这个联合体总计占4个字节,而联合体成员i是int类型的,它占了4个字节,另外一个c是char类型占了1个字节,两个一起占了4个字节。说明c和i必然有一处是共用一块空间的,再者有u本身和它的两个成员是一个地址如上图003EFA80,说明首地址是重合的,简易示图如下:

 可以再看这一段C语言共用体(C语言union用法)详解 (biancheng.net)

int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;
}

           return un.c就是返回第一个字节,我们知道vs是小端,低字节存放在低地址,那么返回1就说明小端存储模式。

1.8 整形提升

1.8.1 整形提升的陷阱

以下代码的运行结果?

#include <stdio.h>
int main()
{
	char a = -1;
	//1000 0000 0000 0000 0000 0000 0000 0001
	//1111 1111 1111 1111 1111 1111 1111 1110
	//1111 1111 1111 1111 1111 1111 1111 1111
	//1111 1111 - a - 放在内存中

	signed char b = -1;
	unsigned char c = -1;
    printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}

注意:

        打印的时候,计算机存的是补码,但是我们打印出来的是十进制,我们得把补码转成原码,然后将补码转成原码,然后把原码转成十进制数,得到的结果才和打印出来的十进制数一致。

打印的时候才考虑abc的类型
a变成%d打印,char->int, 发生整形提升
1111 1111被认为符号位是1,所以高位补1
1111 1111 1111 1111 1111 1111 1111 1111 - 整形提升后的补码

但是我们打印出来的是十进制,我们得把补码转成原码,

所以将整形提升后的补码转换成原码:
1000 0000 0000 0000 0000 0000 0000 0000 - a=-1的反码
1000 0000 0000 0000 0000 0000 0000 0001 - a=-1的原码

然后把原码转成十进制数,打印出来:-1
signed char 同 char

而对于unsigned char c:

0000 0000 0000 0000 0000 0000 1111 1111 - 无符号,高位补0
原码反码补码相同 - 》然后把原码转成十进制数,打印出来:255

 1.8.2 无符号整形打印

a的补码是1000 0000

但是先要进行整形提升变成int类型,这样就需要高位补1,因为现在认为1是符号位,所以补1,变成:  1111 1111 1111 1111 1111 1111 1000 0000

而我们打印又是以%u无符号打印的,所以直接源码反码补码相同,即认为没有符号位了,直接打印出他的十进制数即4294967168

#include <stdio.h>
int main()
{
	char a = -128;
	
	printf("%u\n", a);

	return 0;
}

    1111 1111 1111 1111 1111 1111 1000 0000
    1000 0000(a)
    内存中存放的样子
    要打印,发现a是char类型,是signed char

    而打印的类型是unsigned int
    故从char 到 int,进行整形提升是补符号位1
    因为认为符号位是1,从1000 0000

     提升后变成:
    1111 1111 1111 1111 1111 1111 1000 0000
    上值又被认为是无符号的,所以补码反码原码都是本身

       原码再变成二进制:

1.8.3 补码相加原则

#include<stdio.h>
int main()
{
	int i = -20;
	//10000000 00000000 00000000 00010100
	//11111111 11111111 11111111 11101011
	//11111111 11111111 11111111 11101100
	unsigned int j = 10;
	//00000000 00000000 00000000 00001010
	printf("%d\n", i + j);
	//11111111 11111111 11111111 11110110 - 补码相加
	//10000000 00000000 00000000 00001001
	//10000000 00000000 00000000 00001010 - -10
	return 0;
}

1.8.4 死循环--无符号的陷阱

#include<stdio.h>
#include<windows.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
		Sleep(1000);
	}
	return 0;
}

1.8.5 无符号字符型数组的长度

a[i]的范围在-128到+127之间,当i=127的时候,a[127] = -128,i继续加1,a[128] 变成127

#include<stdio.h>
int main()
{
	char a[1000];
	//有符号char
	//-128 —— +127
	//1111 1111
	//1000 0000是无法计算的,直接解析为-128
	//当unsigned char为0-255
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));//255
	return 0;
}

2.结构体

结构体传参传地址

#include<stdio.h>
typedef struct S
{
	int arr[1000];
	float f;
	char ch[100];
}S;

void print(S* ps)
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ",ps->arr[i]);
	}
	printf("\n");
	printf("%f\n",ps->f);
	printf("%s\n",ps->ch);
}
int main()
{
	S s = { {1,2,3,4,5,6,7,8,9,10},5.5f,"hello,world"};
	print(&s);
	return 0;
}

【C语言进阶篇】看完这篇结构体文章,我向数据结构又进了一大步!(结构体进阶详解)-腾讯云开发者社区-腾讯云 (tencent.com)icon-default.png?t=N7T8https://cloud.tencent.com/developer/article/2374086

  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
  • 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
  • 所以结构体传参的时候一定要传地址!

🔥 由于函数在传递参数时,如果我们传的是实参,那么形参将是实参的一份临时拷贝。如果我们传过去的结构体很大,那么形参也要开辟相同大小的空间就会照成空间的浪费!

总结

C语言复习

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值