1:传值和传址区别
按值传递:
- 在调用函数中将原函数的值拷贝一份过去给被调用的函数,在被调用函数中对该值的修改,不会影响原函数的值。
- 值传递,变量赋值,修改变量的值------修改的是新地址(复制地址)的内容,并不会影响到原来地址的数据。
- 在函数调用中,形参是实参内容的拷贝,并不是地址的拷贝,所以改变形参的值,并会影响实参的值。
按地址传递:
- 在调用函数的时候将原函数的值所在的地址拷贝一份过去,被调用函数对这个地址所作的修改会影响原理的值。
- 地址传递:用指针修改变量的值,就是把原地址中的值修改了,就相当于对实参本身进行的操作。
1.1:概述:变量的地址和地址中的内容
首先我们要知道:变量a的地址和 变量a地址中的内容的区别,数据是存放在内存中的,每一个变量都有一个内存地址,变量的内容存放在对应内存地址的空间中。
如:定义 :
int a = 10 ;
那么a在内存中的地址是 ox1100 , 在这个地址中存储的数据就是 10 。
![]()
假设创建指针 p ,把a的地址赋值给 p ,就是把 a的首地址 ox1100 赋值给 指针p ,这个时候p的值就是变量a在内存中的首地址。
int a = 10;
int *p;
p = &a;
1.2 :案例:值传递
新开辟一个内存空间,存储原理变量的值,修改变量修改的是新内存空间中的值,所以,原始的参数是不会被函数修改的。
优点: 通过值传递传的参数可以是内置类型参数:数字,字符等。原本参数的值是不会被修改的。
缺点:不能修改原参数的值。
#include<iostring>
#include<string>
uising namespace std;
void swap(int x, int y) {
int temp;
temp = x;
x = y;
y = temp;
}
int main(){
int a = 4, b = 6;
printf("交换前:n a = %d, b = %d\n", a , b);
swap(a,b);
printf("交换后:n a = %d, b = %d\n", a , b);
return 0;
}
1.3: 址传递
址传递:就是指针传递,形参实际是指向实参地址的指针,当对形参进行操作时,就相当于对实参本身进行操作。
可以改变指针指向内容的值,但是不能改变指针本身的地址。
#include<iostream>
#include<string>
using nameapace std;
void swap(int* x, int* y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main(){
int a = 4,b = 6;
printf("交换前:a = %d ,b = %d", a, b);
swap(&a, &b);
printf("交换后:a = %d ,b = %d", a, b);
}
1.4:案例:面试题 :看下面面试题
#include<iostream>
#include<string>
using namespace std;
void getMemory(char* p) {
p = (char*) malloc(100);
}
int main(){
char* str = NULL;
getMemory(str);
// 运行会报错:程序会崩溃,没有输出(因为str 始终是 NULL)
strccpy(str, "hello wodl");
printf(str);
}
- 解析:函数getMemory() 并不能动态传递内存,main函数中的 str 一直是 NULL,strcpy(str, "hello world" ); 将使程序崩溃,无法输出。
- 其实函数getMemory()的形参p 是实参数str的一份拷贝,函数中的操作都是针对 p指向的内存地址进行的,实参之前 指向的内存地址是NULL, 后面仍然是 NULL ,所以输出 *str的值就会产生崩溃
解决方案
- 如要要解决上述问题:我们可以将形参从 一重指针修改为 二重指针。
- “二重指针指向一重指针的地址”: 也就是说:传递过来的即使就是 *str本身
#include<iostream>
#include<string>
using namespace std;
void getMemory(char** p) {
p = (char*) malloc(100);
}
int main(){
char* str = NULL;
getMemory(str);
// 运行会报错:程序会崩溃,没有输出(因为str 始终是 NULL)
strccpy(str, "hello wodl");
printf(str);
}
2:函数的作用域和范围
案例:return 不可返回指向栈内存的指针。
#include<string>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// https://blog.csdn.net/qq_43331089/article/details/127529099
// 问题: return 不可返回指向栈内存的指针
vector<int> *fibon_seq(int size) {
if (size <=0 || size > 1024)
{
cout << "waring: fibon_seq(): " << size << "not supported ----reseting to 8\n";
size = 8;
}
vector<int> elems(size);
for (int i = 0; i < size; i++)
{
elems[i] = i + 1;
}
cout << "local elems address: " << &elems<< endl;
return &elems;
}
int main() {
vector<int> *v = fibon_seq(10);
// 此处 指针v 保存的内存地址就是函数: fibon_seq()局部变量elems 内存地址
cout << "local elems address 1: " << v;
// error: address of local variable 'elems' returned [-Wreturn-local-addr]
int a = v->at(0);
}
1: vector<int> *v = fibon_seq(10); 指针v 保存的内存地址就是函数: fibon_seq()局部变量elems 内存地址 。
2:但是局部变量在函数fibon_seq()执行完毕后,就会释放。
3:所以v->at(0) 再从这个地址取数据,由于空间被释放,所以会出现异常。
解决方案
1:我们可以使 fibon_seq()函数返回 一个 vector 对象
2:返回的这个对象其实是: fibon_seq()函数局部变量 elems 副本
3:函数执行完毕 源对象:elams被回收,副本对象仍然保留。
2.1:简述:return不可返回指向栈内存的指针原因以及修改方案。
数据报错在静态存储区与动态存储区的区别就是:静态存储区在编译---链接阶段就已经确定,程序运行过程中不会在变化,只有当程序退出的时候,静态存储区的内存才会被系统回收,动态存储区是在程序运行过程中动态分配的。
而静态存储区包括:文字常量区,全局数据区,静态变量等等,都归静态存储区这一大类。
举例:先看一下return返回指向栈内存指针的例子:
char* GetStr() {
// 保存在栈中
char p[] = "hello";
return p;
}
int main() {
char* str = NULL;
str = GetStr();
printf("%s\n", str);
}
$ g++ -c main.cpp -o main.o
main.cpp: In function 'char* GetStr()':
main.cpp:28:7: warning: address of local variable 'p' returned [-Wreturn-local-addr]
char p[] = "hello";
^编译运行,可以看到如上警告。
和我们预期输出字符串 Hello 并不相符。
原因:
1:因为 Getstr 函数返回指向栈内存的指针,这里的变量 p是局部变量,而局部变量时分配在栈上的。
2: Hello 字符串即保留在栈内存上,栈内存在函数调用结束会自动销毁,因此此时的 p 里的内容是未知的,所以结果无输出。
2.1.1 修改方案 :将Hello 存储在静态区(常量区)
const char* GetStr() {
// 保存在栈中
const char *p = "hello";
return p;
}
int main() {
const char* str = NULL;
str = GetStr();
printf("%s\n", str);
}
// 打印结果 :Hello
问题: 同样是字符串 为什么一个在栈上,一个在静态区。
1: char *p = "Hello" ----- 首先定义了一个指针变量 p , 编译器就会为指针变量开辟栈空间,但是此时并没有空间来存在 Hello , 所以 Hello 只能存储在静态区。
2:char p[] = "Hello" -----此处首先定义一个数组 p ,因为未给出数组大小,所以数组大小未确定,然后把 Hello 保存在这个数组里,编译器就会为数组 p 开辟适当的空间来存储 Hello
2.2:修改方案:将 p定义成全局变量
因为全局变量存储在静态存储区,程序结束后才会释放,但是这样会导致函数是不可重如的。
int* p = new int;
int* GetInt() {
*p = 1;
return p;
}
int main() {
int* i = GetInt();
printf("%d\n", *i);
}
// 打印内容: 1
2.3:修改方案:使用malloc申请动态内存
在 Getstr函数中使用 malloc 申请动态内存后,使用完毕需要通过 free进行释放,否则会导致内存泄漏。
2.4:修改方案:将变量p声明为static 静态变量。
char* Getstr2() {
static char p1[] = "hello111";
return p1;
}
int main() {
const char* str = NULL;
str = Getstr2();
printf("%s\n", str);
}
// 打印结果: hello111
3:动态内存管理
1:栈又叫堆栈 -----》非静态局部变量/ 函数参数/ 返回值等, 栈是向下增长的
2:内存映射段是高效的 I/O映射方式,用于装载一个共享的动态内存库,用户可使用系统接口创建共享内存,做进程间的通信
3:堆用于程序运行时动态内存分配,堆是可以上增长的
4:数据段----存储全局数据和静态数据
5:代码段----可执行代码/ 只读常量。
3.1:new/ delete
int main()
{
int* p1 = new int; // 在堆区申请一个 int大小的空间,不会初始化
int* p2 = new int(0) // 在堆区并初始化为0
delete p1;
delete p2;
int* p3 = new int[10] // 在堆区申请一块10个int 大小空间,未初始化
int* p4 = new int[10]{1,2,3,4} // 初始化为 {1,2,3,4,0,0,0,0,}
delete[] p3
delete[] p4
}
new/delete 和 malloc/ free 区别
1:对于内置类型,没有区别
2:new 和delete是C++的关键字/ 操作符,而malloc和free 是C语言的库函数
3:对于自定义类型,相比于malloc 和free ,new和delete会额外调用类中的构造函数和析构函数
4: malloc 的返回值是 void* 使用时需要强转,new 后边跟的是空间的类型,所以new不需要强转。
5:malloc 失败返回空指针,需要判空;new失败抛异常,需要捕获异常。
3.2 new的原理 、delete原理
new等价于 operator new()+ 构造函数。 operator new() 不是new 运算符的重载,它是库里的一个全局函数。
其中 operator new 是对malloc 的封装,如果malloc 失败,将会抛出异常。
void *_CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) ==0) {
if(_callnewh(size) ==0) {
// report no memory
// 如果申请内存失败了,这里会抛出 bad_alloc 异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
}
return (p);
}
delete 等价于 operator delete()+ 析构函数 :
- 从底层代码可以看出 operator delete()调用了 free。
- 所以针对内置类型或无资源的类对象 delete时,使用delete和free效果相同。但是对于有资源需要释放的对象时,直接使用free虽然也释放了对象的空间,但对象内部的资源还未被清理,会导致内存泄漏,这种情况必须使用 delete。
// operator delete : 该函数最终是通过 free来释放空间的
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook,(pUserData,0));
if (pUserData ==NULL) {
return;
}
_mlock(_HEAP_LOCK); // block other threads
_TRY
// get a pointer to memory block header
pHead = pHdr(pUserData);
// verify block type
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead -> nBolckUse));
// 调用 free()
_free_dbg(pUserData, pHead->nBlockUse);
_FINALLY
// release other threads
_munlock(_HEAP_LOCK);
_END_TRY_FINALLY
return;
}
3.3: new T[N]原理 和 delete[]原理
new T[N] 原理
- new T[N] 调用 operator new[]
- operator new[] 调用 operator new 完成N个对象空间的开辟
- 调用N次构造函数完成N个对象的初始化。
delete[] 原理
- 调用N次析构函数完成 N个对象资源清理的工作
- 调用 operator delete[]
- operator delete[] 调用operator delete完成整段空间的释放