我与阿C的纸短情长(二)
1.普通函数与宏函数的区别
1.编译时机:普通函数在编译时被编译器处理,函数调用会被替换为对函数的实际调用。宏函数在预处理阶段被处理,宏会在代码中被简单地展开,而不是被调用。
2.参数处理:普通函数的参数会被求值一次,且类型会被检查。宏函数的参数在展开时直接替换到宏定义中,不会进行类型检查,可能导致意外的行为。
3.代码大小:普通函数会生成独立的函数代码,增加了代码段的大小。宏函数在展开时直接将代码插入到调用点,避免了函数调用的开销,但可能会导致代码膨胀。
4.调试:普通函数有明确的函数调用栈,方便调试。宏函数在展开后,调试时可能会出现调试信息与源代码不匹配的情况。
5.作用域:普通函数有自己的作用域,不会影响其他代码。宏函数在展开时直接替换,可能会对代码的作用域造成影响。
2.C语言的基本类型有哪些(32位系统),占用字节空间数(要熟练)
char | 1 |
short int | 2 |
int/long int | 4 |
char * /int * /任何的指针 | 4 |
float | 4 |
double | 8 |
3.头文件#ifndef/#define/#endif的作用
- #ifndef:用于判断当前头文件是否已经被包含。
- 如果该宏之前没有被定义过,则继续编译下面的代码。
- 如果该宏之前已被定义过,则跳过下面的代码,直接到 #endif。
- #define:用于定义一个宏。
MY_HEADER_H
表示头文件已被包含。- #endif:用于结束 #ifndef / #define / #endif 块。
- 标记了头文件的结束位置。
通过使用这种组合,可以防止同一个头文件被多次包含,以避免重复定义和编译错误。
举例:
#ifndef MYHEADER_H // 如果 MYHEADER_H 还没有被定义 #define MYHEADER_H // 定义 MYHEADER_H void sayHello(); // 函数声明 const int MAX_VALUE = 100; // 常量定义 #endif // 结束条件编译 上述是一般的使用模板
4.如何判断大小端?
#include <stdio.h> int check_endianness() { unsigned int num = 1; char *ptr = (char *)# if (*ptr) { return 1; // 小端序 } else { return 0; // 大端序 } } int main() { if (check_endianness()) { printf("This system is little-endian.\n"); } else { printf("This system is big-endian.\n"); } return 0; }
在这段代码中,我们创建了一个无符号整数num
并将其地址转换为指向字符的指针ptr
。由于内存中存储数据的方式取决于系统的字节序,我们可以通过检查该指针指向的第一个字节来判断系统的字节序。如果第一个字节存储的是1
,则说明系统是小端序;如果第一个字节存储的是0
,则说明系统是大端序。
5.内存泄漏和内存溢出是什么?
(1)内存溢出:指程序申请内存时,没有足够的内存供申请者使用。或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错Out Of Memory,即所谓的内存溢出。
(2)内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间。一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
6. strcpy和memcpy区别
1.复制的内容不同。
- strcpy只能复制字符串,
- memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2.复制的方法不同。
- strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,如果空间不够,就会引起踩内存。
- memcpy则是根据其第3个参数决定复制的长度。
3.用途不同。
- 通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。
7.sizeof与strlen的区别
sizeof:
- 用于获取数据类型或变量的字节大小。
- 可以接受多种参数,包括数据类型、变量名、数组名等。
- 返回的是整个数据类型或变量占用的内存空间大小。
strlen:
- 用于获取以’\0’结尾的字符串的实际长度。
- 在运行时计算,需要遍历字符串的内容来确定长度。
- 返回的是字符串中的字符个数,不包括字符串结束符’\0’。
举例:
char str[] = "Hello"; size_t size_str = sizeof(str); size_t length_str = strlen(str); // size_str 的值为 6,因为包括字符串 "Hello" 的 5 个字符和结尾的 '\0',共 6 个字节 // length_str 的值为 5,因为字符串 "Hello" 有 5 个字符,不包括结尾的 '\0'
注意事项:
- sizeof返回的是静态的大小,而strlen返回的是实际的字符串长度。
- 在使用strlen时要确保操作的对象是以’\0’结尾的字符串,否则可能出现不确定的结果。
- sizeof可以用于任何数据类型或变量,而strlen只适用于字符串。
8.结构体和共用体的区别:
结构体(struct)和共用体(union)是在C和C++等编程语言中用来组织和存储数据的重要概念,它们之间有几个关键的区别:
1.结构体(struct):
- 结构体是一种用户自定义的数据类型,可以包含多个不同类型的成员变量。
- 结构体的各个成员在内存中是按照定义的顺序依次存储的,每个成员有自己的内存空间。
- 结构体的大小等于其所有成员变量大小之和,可能会有内存对齐的问题。
- 结构体的各个成员可以同时被访问和操作。
2.共用体(union):
- 共用体是一种特殊的数据结构,所有成员共享同一块内存空间。
- 共用体的大小等于其最大成员的大小,因为共用体中只有一个成员可以被同时使用。
- 当一个共用体的成员被赋值后,其他成员的值会被覆盖,因为它们共享同一块内存空间。
- 共用体通常用于节省内存空间,或者在需要在不同类型的数据之间进行转换时使用。
9.一个指针可以是volatile吗
一个指针可以被声明为volatile
。在C和C++中,volatile
是一个关键字,用于告诉编译器不要对声明为volatile
的变量进行优化,因为这些变量的值可能在程序的控制之外被改变。
当一个指针被声明为volatile
时,意味着指针所指向的数据是volatile
的,即这些数据可能会在程序的执行过程中被外部因素改变,如硬件中断、多线程操作等。因此,编译器不应该对这些数据的读写进行优化,而应该每次都从内存中读取或写入数据。
示例:
volatile int *ptr; // 声明一个指向volatile int的指针 int main() { int a = 10; ptr = &a; // 在程序的其他地方可能会改变a的值,编译器不能对ptr所指向的数据做优化 // 因此,每次访问*ptr时都应该从内存中读取a的值 int b = *ptr; // 从内存中读取a的值 }
10.常见变量定义以及解释:
- int a; // 定义一个变量 a,类型为 int
- int *ptr_a;// 定义一个指针 a,指向 int 类型的变量
- int **ptr_ptr_a;// 定义一个指针 a,指向一个指向 int 类型的指针
- int arr[10];// 定义一个数组 a,有 10 个元素,每个元素是 int 类型
- int *ptr_arr[10];// 定义一个数组 a,有 10 个元素,每个元素是 int 类型的指针
- int (*ptr_to_arr)[10];// 定义一个指针 a,指向一个数组,该数组有 10 个元素,每个元素是 int 类型
- int (*ptr_to_func)(int);// 定义一个指针 a,指向一个参数是 int,返回值是 int 的函数
- int (*arr_of_func[10])(int);// 定义一个数组 a,每个元素是一个指向参数是 int,返回值是 int 的函数指针
11.说说下面表达式的区别
1. const int* p; 2. int* const p; 4. int const *p; 5. const int* const p;
- const int* p; 这表示指针 p 指向一个常量整数。这意味着你不能通过 p 修改所指向的整数的值,但是你可以改变 p 指向另一个整数。
- int* const p; 这表示 p 是一个指向整数的常量指针。这意味着你可以通过 p 修改所指向的整数的值,但是你不能改变 p 指向另一个整数。
- int const *p; 这和第一个声明 const int* p; 是等价的,表示指针 p 指向一个常量整数。
- const int* const p; 这表示 p 是一个指向常量整数的常量指针。这意味着你既不能通过 p 修改所指向的整数的值,也不能改变 p 指向另一个整数。