C学习:小白入门C语言常见疑问汇总

本文记录个人自己总结的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 longlong 不同。


枚举时,常量占多大空间大小?还是说与编译器有关?

typedef enum {
   
 E12 = 4,
 E13 = 3,
 E14 = 2,
 E15
}

答: 与编译器有关。以上代码sizeof(E1)具体大小不确定,有可能4个也有可能8个byte,要看数据模型和编译器。未指定的值只能在前面值上自动递增1,而不能递减。