(自用)
1.sizeof和strlen的计算
答案详见博客
1.一维数组
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
2.字符数组
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
return 0;
}
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
}
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
}
int main()
{
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
return 0;
}
3.二维数组
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0] + 1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
}
int arr[3][4] = { 0 };
printf("%d\n", sizeof(*arr+1));//?
2.指针笔试题
1.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
结果:
2.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<string.h>
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
结果:
整形指针加1,跳过1个整形,跳过4个字节;字符指针加1,跳过1个字符,跳过1个字节。而这里是自己定义的结构体,结构体大小为20,那么这里+1则跳过20个字节。那么p的值为0x100000时
1.p+0x1,对于结构体类型的p进行加1,需要跳过20个字节,对于16进制则为14,因此结果为0x100014。
2. (unsigned long)p + 0x1 将结构体指针类型强制类型转换为unsigned long类型,16进制加减正常+1,因此结果为00100001。
3.(unsigned int*)p+0x1:将结构体指针类型强制类型转换为unsigned int*类型,是一个指针,需要增加一个指针的大小,即+4,结果为00100004。
3.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
结果:
分析:
(int*)(&a+1):首先&a取出整个数组的地址进行加+1,也就是跨越了一个数组,将其转化为int*类型在赋给指针变量ptr。
(int)a+1:a此时为首元素地址(假如a的其实地址为0x0012ff40),a强制类型转换为int类型,+1则正常数字运算,对地址的值进行加1,跳过1个字节,运算后(0x0012ff41)。如果是int*类型的话,+1跳过4个字节,(假如a的其实地址为0x0012ff40,则+1后变成0x0012ff44)
4.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
// 1 3 5
//逗号表达式的结果是最后一个表达式的结果
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
这道题考察了逗号表达式(0,1),(2,3),(4,5)为逗号表达式,取逗号最右边的结果,即1,3,5,此时二维数组变成了a[3][2]={1,3,5};也就是第一行为a[0]={1,3},a[1]={5,0},a[2]={0,0};
a[0]是二维数组第一行的数组名,对a[0]这个数组名没有&,没有单独sizeof,所以a[0]这个数组名表示数组首元素的地址,即a[0][0]的地址,a[0]---->&a[0][0]
因此p=a[0][0],p指向为首元素地址,p[0]=1
5.
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
结果:
&p[4][2] - &a[4][2]中间相差4个元素,而&p[4][2]为低地址处所以为-4。-4的补码在内存中存储,且被看作为地址:
10000000000000000000000000000100 //原码
111111111111111111111111111111111011 //反码
111111111111111111111111111111111100 //补码
%p转换成16进制输出(每4位二进制位为1位十六进制位),则转换为0xFF FF FF FC
6.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
分析:
&aa+1:&aa取出的是整个数组的地址,加1跳过整个数组的大小,指向整个数组的末尾。
*(aa+1):aa是数组首元素的地址,加1后aa+1为数组第2行元素的地址,即aa[1],
然后都-1因为是int*所以指针都往前4个字节,得到结果
7.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
char*a[]数组存放三个元素,分别是字符串a[0],a[1],a[2];a为首元素地址存放在char* *p指针中,pa=a[0],然后pa++,则pa此时指向的是a[1],有首元素地址打印在\0处结束。
8.
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
操作顺序: 先++,后解引用
指针cpp存储了cp的地址,因此初始先指向cp[0],之后指针cpp到cp[1](也就是红色箭头指向),cp[1]存储的是c+2的地址,因此有了c[2]内容的首元素地址,打印c[2]至'\0'
操作顺序: ++,解引用,再--,解引用,最后解引用位置+3
首先++--的优先级最高根据结合远近,先对cpp进行++,但是注意由于上一题++*cpp,是对cp自加并且将自加的值赋给cpp,因此cpp的位置从上一题的位置开始。++cpp后解引用为*cp[2],cp[2]的内容为c+1的指针(也就是c+1的地址),而c+1的地址-1再解引用此时为c[0],而之后+3则指针指向第三个元素的后面,最后从该位置打印至结束。
操作顺序:cpp[2]-2到cpp[0],对c[0]指向c[3]对c[3]解引用,再向后移动3个单位也就是第三关字母的后面开始进行打印
从上个题目的位置,再-2到cpp[-2]==*(cpp-2)==cp[0],cp[0]存储的是c+3的地址,之后对c+3的地址解引用,先指向首元素地址,再+3,在第三个元素的后面,最后从该位置打印至结束
cpp[-1][-1]==*(cpp-1)[-1]==cp[1][-1]==*((c+2)-1)==*(c+1)==c[1],对cpp-1地址解引用得到c+3,后解引用在-1,解引用得到NEW,指针指向首元素地址,+1改变指针指向,最后打印得到结果
3.写一个函数返回参数二进制中的1的个数
比如:15 0000 1111 4个1 (一般讨论的是补码,原码在计算机没办法存储).二进制中1的个数
int count_one_bit(int num)
{
int count = 0;
while (num)
{
if (num % 2 == 1)
{
count++;
}
num /= 2;
}
return count;
}
int main()
{
int num = 0;
scanf("%d", &num);
int ret = count_one_bit(num);
printf("%d\n", ret);
return 0;
}
当输入-1时,输出结果为0,这个函数有缺陷,只能计算大于0的数字。
对于-1来说存在二进制序列中为:
10000000 00000000 00000000 00000001 //原码-1
11111111 11111111 11111111 11111110 //反码-1
11111111 11111111 11111111 11111111 //补码-1
解法1.如果稍作修改变成unsigned int,则-1在内存中每一位都变成有效位。
int count_one_bit(unsigned int num)
{
int count = 0;
while (num)
{
if (num % 2 == 1)
{
count++;
}
num /= 2;
}
return count;
}
int main()
{
int num = 0;
scanf("%d", &num);
int ret = count_one_bit(num);
printf("%d\n", ret);
return 0;
}
解法2:按位与1检测二进制1的个数(1&1=1,1&0=0)
int count_one_bit(unsigned int num)//int num也可以都是按位与,上一个是因为num除法运算有问题
{
int count = 0;
for (int i = 0; i < 32; i++)
{
if ((num >> i) & 1 == 1)
{
count++;
}
}
return count;
}
解法3:n=n&(n-1)
只要运算一次,n的二进制中最右边的一个1就会消失
代码:
int count_one_bit(unsigned int num)
{
int count = 0;
while (num)
{
num = (num - 1) & num;
count++;
}
return count;
}
4.获取一个整数二进制序列中所有偶数位和奇数位,分别打印出二进制序列
int main()
{
int num = 0;
int i = 0;
scanf("%d", &num);
for (i = 31; i >= 1; i-=2)
{
printf("%d", (num >> i) & 1);
}
printf("\n");
for (i = 30; i >= 0; i -= 2)
{
printf("%d", (num >> i) & 1);
}
printf("\n");
}
结果:
5.求两个数二进制中不同位的个数
编程实现:两个int(32)位整数m和n的二进制表达中,有多少个位bit不同?
int main()
{
int m = 0;
int n = 0;
int count = 0;
scanf("%d %d", &m, &n);
int ret = m ^ n;
while (ret)
{
ret = ret & (ret - 1);
count++;
}
printf("%d", count);
}
结果:
注:异或^
010异或001得到011
6.如何判断一个数是不是2的幂次方
注:2的幂次方的数,二进制中只有1个1
2-10 、4-100、8-1000、16-10000
解:只需判断表达式n&(n-1)是否等于0
7.下面代码的结果是:( )
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i)+(++i)+(++i);
printf("ret = %d\n", ret);
return 0;
}
答案:程序错误
表达式(++i)+(++i)+(++i),只有操作符的优先级和结合性,没法确定唯一计算路径。所以这个表达式可能因为计算顺序的差异导致结果是不一致的,所以表达式是错误的表达式。可以在VS和Linux gcc测试,结果可能有差异。
8.下面代码的结果是:
#include <stdio.h>
int i;
int main()
{
i--;
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
C语言中,0为假,非0即为真。
全局变量,没有给初始值时,编译其会默认将其初始化为0。(即i的值为0)
i的初始值为0,i--结果-1,i为整形,sizeof(i)求i类型大小是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形(unsigned int),因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形是一个非常大的数字,超过4或者8,故实际应该选择A
9.判断
在一个函数内复合语句中定义的变量在本函数范围内有效(复合语句指函数中的成对括号构成的代码)(?)
错误,复合语句中定义的变量只能在复合语句中使用。
10.BC117 小乐乐走台阶
小乐乐上课需要走n阶台阶,因为他腿比较长,所以每次可以选择走一阶或者走两阶,那么他一共有多少种走法?
#include <stdio.h>
int Fib(int n)
{
if (n <= 2)
{
return n;
}
else
{
return Fib(n - 1) + Fib(n - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return
11.BC38 变种水仙花
#include <stdio.h>
int main(){
for(int i=10000;i<100000;i++){
int a=0;
int b=0;
int c=10;
int sum=0;
int x=i;
while(x/c!=0){
a=x%c;
b=x/c;
sum+=a*b;
c*=10;
if(x==sum){
printf("%d ",x);
}
}
return 0;
}
12.下面代码的结果是:( )
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5};
short *p = (short*)arr;
int i = 0;
for(i=0; i<4; i++)
{
*(p+i) = 0;
}
for(i=0; i<5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
答案:
13.错误提醒
1.debug和release的环境下存放变量的不同
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i <= 12; i++)
{
printf("hehe\n");
arr[i] = 0;
}
return 0;
}
在debug版本下,随着数组下标的变化,地址是由低到高变化的。而开辟空间是先使用低地址再使用高地址,如果数组下标越界可能会导致程序死循环。release版本在空间存储变量由低到高,因此循环不会出现问题。
2. int* p=(int*)0x0012ff40和int a =0x0012ff40中的0x0012ff40区别
其中,0x0012ff40都是数值,而前者变成int*指针类型的数值,相当于地址存放在p中,而后者只是一个普通数值存放在a中。
3.指针-指针得到是指针和指针之间的字节个数(?)
错误,两个指针相减,指针必须指向一段连续空间,减完之后的结构代表两个指针之间相差元素的个数
4.指针不能比较大小(?)
指针中存储的是地址,地址可以看成一个数据,因此是可以比较大小的
14.打印水仙花数
求出0~100000之间的所有“水仙花数”并输出。
“水仙花数”是指一个n位数,其各位数字的n次方之和确好等于该数本身,如:153=1^3+5^3+3^3,则153是一个“水仙花数”
#include <stdio.h>
#include <math.h>
int main()
{
int i = 0;
for(i=0; i<=999999; i++)
{
int count = 1;
int tmp = i;
int sum = 0;
//判断i是否为水仙花数
//1. 求判断数字的位数
while(tmp/10)
{
count++;
tmp = tmp/10;
}
//2. 计算每一位的次方和
tmp = i;
while(tmp)
{
sum += pow(tmp%10, count);
tmp = tmp/10;
}
//3. 判断
if(sum == i)
printf("%d ", i);
}
return 0;
}
结果:
15.关于Debug和Release的区别说法错误的是:( )
A.Debug被称为调试版本,程序调试找bug的版本
B.Release被称为发布版本,测试人员测试的就是Release版本
C.Debug版本包含调试信息,不做优化。
D.Release版本也可以调试,只是往往会优化,程序大小和运行速度上效果最优
A:正确,Debug为调试版本,一般在开发完成后发布工程前,调试代码都是在Debug模式下进行的B:正确,Release版本最终是要发送给用户的,发给用户的版本必须要没有问题,测试人员就是最后一个把关的C:正确,Debug版本是调试版本,编译器编译时会增加一些调试信息,编译器基本不会对其进行优化D:错误,Release版本是不能调试的,一般都是在Debug版本下调试的,Release版本一般编译器会进行大量的优化,删除无用的代码,指令的次序调整等,让其速度更快
16.将一个字符串str的内容颠倒过来,并输出。
void Reverse(char* str)
{
char* left = str;
char* right = str + strlen(str)-1;
while(left < right)
{
char temp = *left;
*left = *right;
*right = temp;
++left;
--right;
}
}
int main()
{
char str[] = "hello bit";
Reverse(str);
return 0;
}
// 注意:如果是在线OJ时,必须要考虑循环输入,因为每个算法可能有多组测试用例进行验证,参考以下main函数写法,
int main()
{
char str[101] = {0};
while(gets(str))//fgets(arr,10000,stdin)//stdin - 标准输入-键盘
{
Reverse(str);
printf("%s\n", str);
memset(str, 0, sizeof(str)/sizeof(str[0]));
}
return 0;
}
17.喝汽水问题
喝汽水,1瓶汽水1元,2个空瓶可以换一瓶汽水,给20元,可以喝多少汽水(编程实现)。
*
思路:
1. 20元首先可以喝20瓶,此时手中有20个空瓶子
2. 两个空瓶子可以喝一瓶,喝完之后,空瓶子剩余:empty/2(两个空瓶子换的喝完后产生的瓶子) + empty%2(不够换的瓶子)
3. 如果瓶子个数超过1个,可以继续换,即重复2
*/
int main()
{
int money = 0;
int total = 0;
int empty = 0;
scanf("%d", &money);
//方法1
total = money;
empty = money;
while(empty>1)
{
total += empty/2;
empty = empty/2+empty%2;
}
printf("total = %d\n", total);
return 0;
}