嵌入式系统:C语言基础(一)
1.static关键字
- 声明静态变量,使其生命周期延长或作用域限定在当前文件内。
- 声明静态函数,使其作用域限定在当前文件内。
- 声明静态成员变量,使其属于类本身而不是对象,多个对象共享同一份内存。
- 使用静态限定符,控制变量的初始化和生命周期。
举例:
- 在函数内部使用 static:
#include <stdio.h> void increment() { static int count = 0; count++; printf("调用次数:%d\n", count); } int main() { for (int i = 0; i < 5; i++) { increment(); } return 0; } 在每次调用 increment 函数时,count 的值会持续增加,而不会被重置。 这是因为 count 被声明为 static,其生命周期跨越了函数调用。
2.在文件作用域使用 static:
//这里的例子是防止同学们以后要避免这样去使用。更好的去理解static的隐藏性 // File1.c static int globalVar = 10; //变量只可在file1.c里使用 // File2.c extern int globalVar; int main() { printf("globalVar 的值:%d\n", globalVar); return 0; } 在 File1.c 文件中,我们声明了一个具有文件作用域的静态全局变量 globalVar。 在file2里是extern不到。
2.全局变量和局部变量的区别
全局变量:
- 在函数外部声明的变量,整个程序都可以访问。
- 声明时会被默认初始化,可以在任何函数中使用。
- 生命周期长,整个程序执行期间都存在。
- 全局变量存储在全局数据区(data)中
局部变量:
- 在函数内部或代码块内部声明的变量,只能在所属的函数或代码块中访问。
- 声明时没有默认初始化,需要手动赋值才能使用。
- 生命周期短,只在所属的函数或代码块的执行期间存在。
- 局部变量存储在栈区(stack)
3.普通函数与宏函数的区别
1.编译时机:普通函数在编译时被编译器处理,函数调用会被替换为对函数的实际调用。宏函数在预处理阶段被处理,宏会在代码中被简单地展开,而不是被调用。
2.参数处理:普通函数的参数会被求值一次,且类型会被检查。宏函数的参数在展开时直接替换到宏定义中,不会进行类型检查,可能导致意外的行为。
3.代码大小:普通函数会生成独立的函数代码,增加了代码段的大小。宏函数在展开时直接将代码插入到调用点,避免了函数调用的开销,但可能会导致代码膨胀。
4.调试:普通函数有明确的函数调用栈,方便调试。宏函数在展开后,调试时可能会出现调试信息与源代码不匹配的情况。
5.作用域:普通函数有自己的作用域,不会影响其他代码。宏函数在展开时直接替换,可能会对代码的作用域造成影响。
4.int main(int argc, char ** argv)函数中,参数argc和argv代表什么?
第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数。
第二个参数,char*型的argv[],为字符串数组,用来存放指向字符串的指针元素,每一个指针元素指向一个字符串参数。
各成员含义如下:
- argv[0]指向程序运行的全路径名
- argv[1]指向在DOS命令行中执行程序名后的第一个字符串
- argv[2]指向执行程序名后的第二个字符串。。。。。。
- argv[argc-1]指向执行程序名后的最后一个字符串argv[argc]为NULL
5.#include<> 和 #include""的区别
使用 #include<>:
- 用于包含系统提供的标准库头文件。
- 在编译器的搜索路径中寻找头文件。
- 编译器会先在系统的标准头文件目录中查找,如果找不到则报错。
使用 #include"":
- 用于包含用户自定义的头文件或项目中使用的其他非系统头文件。
- 在当前源文件的相对路径或指定的绝对路径中寻找头文件。
- 编译器会首先在当前源文件所在目录中查找,如果找不到再根据指定的路径查找。
6.C语言的基本类型有哪些(32位系统),占用字节空间数(要熟练)
char | 1 |
short int | 2 |
int/long int | 4 |
char * /int * /任何的指针 | 4 |
float | 4 |
double | 8 |
7.头文件#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 // 结束条件编译 上述是一般的使用模板
8.说说数组和指针的区别
1.概念:
(1)数组:数组是用于储存多个相同类型数据的集合。 数组名是首元素的地址。
(2)指针:指针相当于一个变量,但是它和普通变量不一样,它存放的是其它变量在内存中的地址。指针名指向了内存的首地址。
2.区别:
(1)赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝
(2)存储方式: 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,数组的存储空间,不是在静态区就是在栈上。 指针:指针本身就是一个变量,作为局部变量时存储在栈上。
(3)求sizeof: 数组所占存储空间的内存大小:sizeof(数组名)/sizeof(数据类型) 在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。
9.如何判断大小端?
#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
,则说明系统是大端序。
10、内存泄漏和内存溢出是什么?
(1)内存溢出:指程序申请内存时,没有足够的内存供申请者使用。或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错Out Of Memory,即所谓的内存溢出。
(2)内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间。一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
11. strcpy和memcpy区别
1.复制的内容不同。
- strcpy只能复制字符串,
- memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2.复制的方法不同。
- strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,如果空间不够,就会引起踩内存。
- memcpy则是根据其第3个参数决定复制的长度。
3.用途不同。
- 通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。
欢迎来到《嵌入式知识图谱》专栏!这里是一个专注于涵盖C/C++编程、操作系统原理、数据结构算法、计算机网络技术以及嵌入式软件知识的平台。 在本专栏中,我们将分享关于嵌入式系统开发的最新趋势、实用技巧、行业见解以及面试准备建议。无论您是刚入门的学习者还是经验丰富的专业人士,我们都致力于为您提供有价值的内容,帮助您在嵌入式软件领域取得成功。