嵌入式学习笔记-C语言知识点:栈的作用,C语言函数参数的入栈顺序,C++ 拷贝构造函数,数组名和指针的区别与联系,指针运算,指针和引用
文章目录
- 栈的作用
- **1. 存储临时变量**
- **2. 多线程编程中的作用**
- C语言函数参数的入栈顺序
- **为什么采用自右向左的入栈顺序?**
- **C++ 拷贝构造函数**
- **1. 什么是拷贝构造函数?**
- **2. 默认拷贝构造函数**
- **3. 拷贝构造函数的错误示例**
- **错误原因**
- **正确做法**
- **C++ 中拷贝赋值函数是否可以使用值传递?**
- **正确写法(使用 `const &`)**
- **数组名和指针的区别与联系**
- **1. 数据存储方式**
- **2. 数据访问方式**
- **3. 使用场景**
- **指针运算**
- **1. `p + n`(指针加法)**
- **2. `p - n`(指针减法)**
- **3. `p1 - p2`(指针相减)**
- **指针和引用的异同**
- **相同点**
- **不同点**
- **指针和引用的转换**
栈的作用
1. 存储临时变量
在C语言中,栈用于存储临时变量,包括:
- 函数参数
- 函数内部定义的局部变量
- 函数返回地址
- 寄存器的备份(用于函数调用时的上下文切换)
当一个函数被调用时,函数的返回地址、参数和局部变量都会被压入栈中,函数返回时,这些数据会被从栈中弹出,从而恢复函数调用前的状态。
2. 多线程编程中的作用
栈是多线程编程的基础,每个线程都有自己的专属栈,用于存储该线程执行过程中各个函数的局部变量,同时维护函数调用关系。
操作系统的多线程管理依赖栈,包括:
- 每个线程有独立的栈
- 中断和异常处理机制也使用栈
栈的独立性保证了线程间的局部变量不会相互干扰。
C语言函数参数的入栈顺序
在C语言中,函数参数的入栈顺序通常是自右向左。
示例:
void f(int a, int b, int c);
f(1, 2, 3);
入栈顺序: 3 → 2 → 1
为什么采用自右向左的入栈顺序?
-
支持可变参数函数(如
printf
)- 变长参数位于参数列表的最右侧,固定参数在栈底部。
- 这样可变参数的起始位置可以动态确定。
-
提高寄存器参数优化的灵活性
- 现代编译器优化时,可以优先将前面的参数放入寄存器,提高效率。
C++ 拷贝构造函数
1. 什么是拷贝构造函数?
在 C++ 中,拷贝构造函数是用于创建新对象并初始化为已有对象的副本的特殊构造函数。
语法:
class Example {
public:
Example(const Example &obj); // 拷贝构造函数
};
2. 默认拷贝构造函数
如果未显式定义拷贝构造函数,C++ 会自动生成一个默认拷贝构造函数。
示例:
class Example {
public:
int value;
Example(int val) : value(val) {} // 普通构造函数
Example(const Example &ex) = default; // 默认拷贝构造
};
3. 拷贝构造函数的错误示例
错误代码:
class Example {
public:
Example(int a) : value(a) {}
Example(Example &ex) { value = ex.value; } // 错误:缺少 `const`
private:
int value;
};
错误原因
Example(Example &ex)
缺少const
,导致递归调用Example e2 = e1;
触发拷贝构造,但e1
需要传递给ex
,这又会调用拷贝构造,形成无限递归,最终导致栈溢出。
正确做法
class Example {
public:
Example(int a) : value(a) {}
Example(const Example &ex) { value = ex.value; } // 正确
private:
int value;
};
C++ 中拷贝赋值函数是否可以使用值传递?
不可以!
原因:
- 如果
operator=
的参数是 值传递,那么在调用时会触发拷贝构造函数。 - 这会导致无限递归,最终导致栈溢出。
示例(错误代码):
class Example {
public:
Example &operator=(Example obj) { // ❌ 错误:值传递
value = obj.value;
return *this;
}
private:
int value;
};
正确写法(使用 const &
)
class Example {
public:
Example &operator=(const Example &obj) { // ✅ 正确
value = obj.value;
return *this;
}
private:
int value;
};
数组名和指针的区别与联系
1. 数据存储方式
- 指针: 存储的是地址,本身占 4/8 字节(具体取决于系统)。
- 数组: 存储的是具体的数据,数组名表示第一个元素的地址。
2. 数据访问方式
- 指针: 需要使用
*
进行解引用,如*ptr
。 - 数组: 直接使用下标访问,如
arr[0]
。
3. 使用场景
- 指针: 适用于动态内存管理(如
malloc/free
)。 - 数组: 适用于存储固定大小的数据集合。
指针运算
1. p + n
(指针加法)
指针向前移动 n
个元素大小。
struct Data { int a; double b; };
Data arr[10];
Data *ptr = arr;
ptr = ptr + 1; // 移动 sizeof(Data) 个字节
2. p - n
(指针减法)
指针向后移动 n
个元素大小。
3. p1 - p2
(指针相减)
返回两个指针之间相差的元素个数。
int arr[5];
int *p1 = &arr[4];
int *p2 = &arr[0];
int diff = p1 - p2; // diff = 4
指针和引用的异同
相同点
- 都表示某个变量的地址。
- 都可以用于参数传递,避免拷贝开销。
不同点
指针 | 引用 | |
---|---|---|
是否可更改指向 | 可更改 | 不可更改 |
是否允许 NULL | 可以 | 不允许 |
语法 | int *p = &a; | int &ref = a; |
指针和引用的转换
指针转引用:
int a = 10;
int *p = &a;
int &ref = *p; // 指针转换为引用
引用转指针:
int a = 10;
int &ref = a;
int *p = &ref; // 引用转换为指针