本文记录个人自己总结的C语言学习时感到困惑的一些小问题,较深入的问题会单独写博客总结,希望该分享能对大家有所帮助。
重要问题
不同类型转换原则是什么,是否熟悉?
宏与内联函数的区别?
- 宏在预处理阶段(预编译)进行替换。
- 内联函数在编译阶段进行替换展开,会检测函数入参。
结构体引用时,符号-> 与 .的区别是?
struct Student name;
sruct Student stu[10]; // 结构体数组
struct Student *p;
int year = 10;
// right
p = &name;
p->year = year; // 与下一句命令等价
(*p).year = year; // 对结构体指针解引用后,才能再用点引用
name.year = year;
p = stu; // 取结构体数组首地址
p[1].year = year; // 对结构体指针偏移1位解引用后,用点引用
// wrong
p.year = year;
name->year = year;
-
简要分析:用
->
时,左侧p必须为指针类型。如p为结构体指针时,p->length
用法正确,p.length
错误。如果p为结构体的名字,p->length
用法错误,p.length
正确。 -
详细分析:p为结构体指针时,
p->val
等价于(*p).val
,本质为对类型地址解引用后,加.
取子成员。不加括号时,*p.val
,等价于*(p.val)
,是错误语句。结构体和联合体可以方便的利用指针,使用p->
来替代(*p).
。
整数溢出和反转
- 有符号数 => 溢出
- 无符号数 => 反转
在计算机中,整数存储的长度是固定的(例如32位或64位),当整数之间进行运算时,可能会超过这个最大固定长度,导致整数溢出或反转. 如果int以下存不了,会自动升级成int,但int不能自动升级成long int。
异或和反码的运算符号,以及与补码的区别
^
异或,0^1
结果为1,0^0 1^1
结果都为0。
~
反码,若是无符号数,则二进制全部取反;若是负数,则符号位不变,其余位取反
补码,正数补码为其本身,负数补码为其绝对值取反再加1
ceil/floor/round/int
傻傻分不清楚?
浮点取整函数梳理如下表:
函数名 | 符号 | 功能 | 举例 |
---|---|---|---|
ceil | ⌈𝑦⌉ | 向上取整 | 正数:(4, 5] -> 5;负数:(-6, -5] -> -5 |
floor | ⌊𝑦⌋ | 向下取整 | 正数:[5, 6) -> 5;负数:[-5, -4) -> -5 |
round | ⌊𝑦⌉ | 圆整,四舍五入 | 正数:[4.5, 5.5) -> 5;负数: (-5.5, -4.5] -> -5 |
int | NA | 截断 | 正数:[5, 6) -> 5;负数:(-6, -5] -> -5 |
其余问题
char、signed char、unsigned char有啥区别
char
常用为字符声明,本质为数值,表示范围见ascii码表里[0, 127],共128个值,一般默认为singed char,取值为其子集,格式化输出时按%c
或%s
输出,超出范围的无法按ascii码输出。singed char
有符号数值声明,主要存数,范围[-128, 127]unsigned char
无符号数值声明,主要存数,范围[0, 256]- 三者共同点是都只占1个字节大小
数据模型(LP32 ILP32 LP64 LLP64 ILP64
)是指什么?
参考资料:数据模型的分析,云水整理
类型size_t
的作用是什么?
经测试发现,在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的,这样利用该类型可以增强程序的可移植性。
x << 2 或 x >> 2 操作改变x本身没?
该表达式运算结果为左值,没有改变x,只是将x的拷贝左移动一位。
unsigned b = x << 1;
这样b才能得到x << 1运算后得到的值
p为int类型指针,*p++
操作后,结果是什么?
本质考察的是运算符的结合顺序,++
的结合次序低于*
。所以是先解引用p,再对p地址自增,最后p指向下一个int数据。该语句其实等价于:*p; p++;
那么下面这两个操作也就很好解释了:
a[*res++] = 2;
a[(*res)++] = 2;
第一个语句等价于:a[*res] = 2; res++;
,对res的指针地址移向一个int数据;
第二个语句等价于:a[*res] = 2; (*res)++;
,对res指向的int值自增;
测试代码如下:
#include <stdio.h>
int main()
{
int a[10] = {
0};
int b[2] = {
1, 2};
int *res = b;
printf("%p\n", res);
a[*res++] = 2;
printf("%p\n", res);
printf("%d\n", a[1]);
printf("%d\n", b[0]);
res = b;
printf("\n%p\n", res);
a[(*res)++] = 3;
printf("%p\n", res);
printf("%d\n", a[1]);
printf("%d\n", b[0]);
return 0;
}
输出效果:
0x7ffec71daee8
0x7ffec71daeec
2
1
0x7ffec71daee8
0x7ffec71daee8
3
2
函数返回值为函数内部定义的指针,会引发异常
- 分析:不能返回在子函数定义的指针,因为
该指针地址存储在栈空间里,一旦子函数结束,自动销毁
。若此时再去引用该地址所指向的内容,会引发未知问题。 - 解决:可以选择返回子函数的某个变量值,因为不是地址指针,子函数结束后,拷贝的变量值可以保留。
x86中,long占多大字节?是否和int一样?
是的,都占4字节。long long
和 long
不同。
枚举时,常量占多大空间大小?还是说与编译器有关?
typedef enum {
E12 = 4,
E13 = 3,
E14 = 2,
E15
}
答: 与编译器有关。以上代码sizeof(E1)
具体大小不确定,有可能4个也有可能8个byte,要看数据模型和编译器。未指定的值只能在前面值上自动递增1,而不能递减。