目录
11.const 关键字的作用?const 修饰指针的意义?
12.typedef 关键字的作用?(应用场景:函数指针和函数指针数组)
13.什么是函数指针,什么是指针函数,什么是函数指针数组,什么是数组指针,什么是指针数组?
1.C语言数据类型有哪些?
c语言数据类型分为基本数据类型和复合数据类型,基本数据类型分为数值类型,字符类型,浮点型,void空类型,地址类型(指针)
复合数据类型分为结构体,共用体,枚举
以及bool类型
2.各种数据类型的字节长度及取值范围?字节长度?
不同的编译器在类型所占字节长度可能不太一样
数据类型 | 字节长度 | 取值范围 |
short | 2 | -32768~32767 |
int | 4 | -2^31~2^31-1 |
long | 4 | -2^31~2^31-1 |
long long | 8 | -2^63~2^63-1 |
char | 1 | -128~127 |
bool | 1 | true or false |
float | 4 | ±3.4e±38 (7位有效数字) |
double | 8 | ±1.7e±308 (15位有效数字) |
long double | 16 | -3.4e+4932~1.1E+4932(18位有效数字) |
unsigned short | 2 |
0 ~ 65535
|
unsigned long | 4 |
0 ~ 4294967295
|
unsigned int | 4 |
0 ~ 4294967295
|
unsigned long long | 8 |
0 ~ 2^64 - 1
|
3.sizeof,strlen 的区别?
sizeof 是运算符,strlen 是函数
sizeof 可以用类型做参数,但是strlen只能用char*做参数,结尾必须是\0
4.补码的作用?
补码作用:计算机的运算都是通过加法运算的,没有减法,补码就是为了统一加减。
正数的补码是他本身,负数的补码转换成原码进行计算(-128补码-0)
5.十进制,二进制,16进制的转换?
二进制---->十进制
二进制是基数为2的数制系统,使用两个数字0和1。转换二进制到十进制,可以将每个位的值乘以2的幂次方,幂的指数从0开始,从右到左递增,然后将这些值相加。
例如,二进制数1011
转换为十进制:
- 从右到左,位的值分别是1, 0, 1, 1
- 计算每个位的值:(1 * 2^0 + 0 *2^1 + 1* 2^2 + 1* 2^3 = 1 + 0 + 4 + 8 = 13)
- 因此,二进制
1011
等于十进制的13
十进制---->二进制
将十进制数转换为二进制,可以使用连续除以2的方法,记录每一步的余数,然后将余数逆序排列。
例如,将十进制数13
转换为二进制:
- 13除以2,商为6余1
- 6除以2,商为3余0
- 3除以2,商为1余1
- 1除以2,商为0余1
将余数逆序排列得到1101
,所以十进制的13
等于二进制的1101
。
十进制转十六进制
十六进制是基数为16的数制系统,使用数字0到9和字母A到F(代表值10到15)。将十进制转换为十六进制,也可以使用连续除以16的方法,然后将得到的余数(如果是10到15,则使用相应的字母)逆序排列。
例如,将十进制数200
转换为十六进制:
- 200除以16,商为12余8
- 12除以16,商为0余12(用十六进制的
C
表示)
因此,200
十进制等于C8
十六进制。
十六进制转十进制
十六进制转十进制类似于二进制转十进制的方法,但是使用16的幂次方。例如,十六进制C8
转为十进制:
C
代表12,因此C8
= (12 * 16^1) + (8 * 16^0) = 192 + 8 = 200
二进制与十六进制直接转换
二进制与十六进制之间的转换相对简单,因为16是2的整数次幂((16 = 2^4))。因此,每四位二进制数可以直接转换为一个十六进制数,反之亦然。
- 将二进制转换为十六进制,可以将二进制数每四位一组(从右开始),转换为相应的十六进制数。
- 将十六进制转换为二进制,则是将每一个十六进制数转换为相对应的四位二进制数。
例如,二进制11011101
转为十六进制,分为两组1101
(十进制13,十六进制D
)和1101
(同样是D
),所以是DD
。反过来,十六进制1A
转为二进制是0001
(1)和1010
(A),组合起来是00011010
。
6.C语言内存空间布局?
假设进程的虚拟空间有 4 个 G,那么内存空间中 1G 属于内核空间,3G 属于用户空 间,3G 的用户空间中分为栈空间、堆空间、数据区和代码段。
栈空间保存的是局部变量、函数形参和函数,秉持着先进后出原则(先分配空间 的最后释放),并且由系统决定数据的分配和释放,未初始化的变量在栈上是垃圾值。
堆空间保存的是用户使用 malloc、calloc、ralloc 这样的分配函数分配空间的区域,秉持着先进先出的原则(先分配空间的先释放),由用户来决定数据的分配和释放,未初始化的变量为 0。
数据区又分为:bss 段、or 段、静态数据区;bss 段保存未初始化的全局变量;or 段保存的是只读数据;静态数据区保存的是静态变量。
7.局部变量和全局变量的区别?作用域
第一,在存储空间上,局部变量存储在栈空间,而全局变量存储在数据区。
第二,在作用域上,局部变量只对它所在函数或所在代码块中可见,生命周期在该函数或 者块结束后释放;全局变量全局可见,访问时加 extern 关键字。
第三,使用时注意,未初始化的局部变量是一个垃圾值,而未初始化的全局变量是 0; 局部变量和全局变量重名时,局部变量会屏蔽全局变量。
8.static 关键字的作用?(语法作用,应用场景)
static 既能修饰局部变量,又能修饰全局变量,还能修饰函数。
修饰局部变量变成静态局部变量,延长局部变量的生命周期,使这个变量在执行函数 之后不会被释放,程序结束后才释放。
修饰全局变量变静态全局变量,该全局变量只能在 本文件中访问,不能再其他文件访问。
修饰函数变静态函数,该函数只能在本文件访问, 不能再其他文件访问。
他的意义在于修饰局部变量可以解决全局变量线程不安全问题,用来替代全局变量; 修饰全局变量和函数可以解决多人合作开发时命名冲突问题。
9.extern 关键字的作用?
10.register 关键字作用及使用注意事项?
register 修饰变量,系统会尽可能(寄存器个数有限)将变量的值保存在 cpu 内部的寄存器中,成为寄存器变量,从而省去 cpu 从内存抓取数据的时间,提高了程序的运行效率。一般用于对频繁访问的变量进行修饰,比如循环变量。
使用时注意它只能够修饰局部变量,不能修饰函数和全局变量;不能修饰 cpu 不支持 的数据类型;也不能通过取地址符获取变量的地址。
11.const 关键字的作用?const 修饰指针的意义?
*p = 6;//错误
(*p)++;//错误
p++;//可以
const int num = 5;
int *p = # *p = 12;
printf("p = %d\n",p); //p = 一连串总是会变的数
printf("*p = %d\n",*p); //*p = 12;
printf("num = %d\n",num);//num = 12
printf("p = %p\n",p); //p = 0x7fff026a5e5c
printf("*p = %p\n",*p); //*p = 0xc
printf("num = %p\n",num);//num = 0xc
12.typedef 关键字的作用?(应用场景:函数指针和函数指针数组)
13.什么是函数指针,什么是指针函数,什么是函数指针数组,什么是数组指针,什么是指针数组?
函数指针是一个指针变量,它指向函数的入口地址。这意味着,通过这个指针,我们可以调用函数,就像直接使用函数名一样。函数指针对于实现回调函数、事件驱动程序和函数表特别有用。
它允许程序在运行时决定调用哪个函数。
void myFunction(int a) {
printf("Value: %d\n", a);
}
int main() {
void (*funcPtr)(int) = myFunction;
funcPtr(5); // 通过函数指针调用函数
return 0;
}
指针函数是一种函数,它的返回类型是指针。这意味着,这个函数返回的是一个地址,该地址指向一个特定类型的数据。指针函数在动态内存分配时特别有用。
int* returnPointer(int size) {
int* arr = (int*)malloc(size * sizeof(int));
return arr; // 返回一个指向整型数组的指针
}
函数指针数组是包含多个函数指针的数组。这样的数组让你能够存储指向不同函数的指针,并根据需要调用数组中的任一函数。这对于实现函数的动态调用非常有用。
void func1() { printf("Function 1\n"); }
void func2() { printf("Function 2\n"); }
int main() {
void (*funcArray[2])() = {func1, func2};
funcArray[0](); // 调用func1
funcArray[1](); // 调用func2
return 0;
}
数组指针是指向一个数组的指针。这种类型的指针可以用来指向一个整型数组、字符数组等。数组指针很有用,特别是在动态二维数组和函数参数传递中。
int main() {
int arr[] = {1, 2, 3, 4, 5};
int (*ptr)[5] = &arr; // 指向含有5个整数的数组的指针
printf("%d\n", (*ptr)[2]); // 访问数组的第三个元素
return 0;
}
指针数组是一个数组,其元素都是指针。这种数组可以存储多个地址值,这些地址可以指向int、char或其他任何类型的变量或数组。
int main() {
int val1 = 1, val2 = 2, val3 = 3;
int* arr[3] = {&val1, &val2, &val3}; // 一个整型指针数组
printf("%d\n", *arr[0]); // 访问第一个元素所指向的值
return 0;
}
14.break、continue 区别
跳出当前循环:break
结束本次循环:continue
15.do{}while(0)的作用
在C或C++中,do{}while(0)结构在看起来似乎无意义的情况下,实际上有一些非常实用的用途,尤其是在宏定义中。这种结构使得宏定义在包含多条语句时更加安全和可用。
为什么需要do{}while(0)?
假设你有一个宏定义,需要执行多条语句:
#define MY_MACRO { \
statement1; \
statement2; \
statement3; \
}
如果直接这样使用,可能会在使用宏的地方遇到问题,尤其是在控制流语句中。例如:
if (condition)
MY_MACRO;
else
// other code
这会导致编译错误或逻辑错误,因为宏扩展后的代码破坏了原有的控制流结构。
使用do{}while(0)可以避免这类问题,确保宏总是像一个单独的语句一样扩展,即使它包含多条语句:
#define MY_MACRO do { \
statement1; \
statement2; \
statement3; \
} while(0)
这样,即使在如下情况下使用宏,也不会有问题:
if (condition)
MY_MACRO;
else
// other code
```
因为do{}while(0)构造被视为一个单独的语句块,所以即使包含多个语句,它们也会正确地作为一个整体执行。而while(0)确保了整个循环只会执行一次,不会引入任何循环效果。
其他优点
**防止错误**:当宏定义用作函数调用时,使用分号(`;`)结束可能会导致意外的行为。do{}while(0)确保即使在宏后误加分号也不会出现问题。
**提高兼容性**:这种模式在不同的编译器和环境中表现一致,增加了代码的可移植性。
16.switch case 使用注意事项
**switch 括号里只能是字符形或者整形
**每个 case 分支里,都不能忘记 break
**不要忘记default break 语句
17.goto 语句使用注意事项及应用场景?
18.++i VS i++
int i = 2;
int num = (++i) + (++i) + (i++) + (i++);
//i+i+(i++)+(i++);//i=4(+1+1)---->4+4+(i++)+(i++)
//8+4+(i++)//i=5
//12+5=17//i=6
int num = (++i) + (i++) + (i++) + (++i);
//i+3+(i++)+(++i)//i=4;---+1=3 +1=4----->4+3+(i++)+(++i)
//7+4+(++i)//i=5
//11+6=17//i=6
int num = (i++) + (i++) + (++i) + (i++);
//2(i=3)+3(i=4)+5+5(i=6)=15
printf("%d,%d,%d,%d,%d\n",i++,++i,i++,++i,i++);//67472
总结:(6,i,4,i,2)-----67472
char *ptr = "helloworld" printf("%c,%c,%c,%c,%c\n",*(ptr++),*(++ptr),*(++ptr),*(ptr++),*(ptr++));//w,w,o,l,d
//w,w,o,l,d