《高质量c++和c编程》7.4 指针参数是如何传递内存的一节中写道

void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, ); // str 仍然为 NULL
strcpy(str, "hello"); // 运行错误
}

无法返回内存,可以用如下方式

void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, ); // 注意参数是 &str ,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}

个人的理解就是,实际上指针传递仍然是一种值传递,只不过在参数是指针的时候,传递的是指针的副本,这样你在地址上的操作实际就反映到了内存中,举个例子来说,假设有一个函数

void fun(int  *p)
{
p = new int;
}

当用调用时fun(q),会产生实参的一个副本设为_p,函数体为副本_p分配了内存,实际上并未改变实参p,这就是GetMemory没有成功的原因。相反,如果我们有如下函数

void fun(int *p)
{
*p = ;
}

在这个函数中,当发生实参调用的时候,仍然会产生实参的副本,但是注意这里不是改变副本,而是改变副本指向的内存中的内容,这里p是一个整形指针,在内存中占四个字节,副本和实参指向同一片内存,所以当你

在以副本为地址的内存内赋值3,实际也就是改变了实参指向的内存中的内容。

总结一下就是:指针传递仍然是值传递,所以我们在函数体内只有操作*p才会达到我们的指针传递要求,而不是操作p,这样操作只在副本上,实际并不反映到实参指向的内存。

书中另一种方法是:

char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof
return p;
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3();
strcpy(str, "hello");
cout<< str << endl;
free(str);
}

实际是将堆的特性和return相结合,堆上分配的内存在函数不会释放,而return实际返回的p的一个副本,但是这里的副本是一个指针,简单的说是一个内存地址,而这个地址在函数结束后并没有释放,所以,我们可以继续

使用。如果是普通的局部变量,return返回它的一个副本,随后局部变量随着函数的结束而被释放,这在某些时候会引起麻烦,比如

char *GetString(void)
{
char p[] = "hello world";
return p; // 编译器将提出警告
}
void Test4(void)
{
char *str = NULL;
str = GetString(); // str 的内容是垃圾
cout<< str << endl;
}

至于return似乎还有东西说,一时想不起。。。

事情总有例外,今天小妞找我调试程序,发现了一件很奇特的事情,看代码

void getArray(char **s, int N)
{
std::ifstream in("test.txt");
if (!in.is_open())
{
std::cout<<"error"<<std::endl;
}
int i = ;
while(i < N)
{
s[i] = (char*)malloc(sizeof(char)*);
in.getline(s[i], );
++i;
}
in.close(); }
int main()
{
char **s = (char **)malloc(sizeof(char) * );
getArray(s, );
for (int i=; i<; i++)
{
std::cout<<s[i]<<std::endl;
}
return ;
}

这个程序可以正确编译执行。而下面代码

void getArray1(char **s, int N)
{
// s = (char **)malloc(sizeof(char) * 4);
s = new char*[];
std::ifstream in("test.txt");
if (!in.is_open())
{
std::cout<<"error"<<std::endl;
}
int i = ;
while(i < N)
{
// s[i] = (char*)malloc(sizeof(char)*100);
s[i] = new char[];
in.getline(s[i], );
++i;
}
in.close(); } int main()
{
char **s;
getArray1(s, );
for (int i=; i<; i++)
{
std::cout<<s[i]<<std::endl;
}
return ;
}

这个代码确实在运行时出错

分析了一下,个人认为虽然两个函数的参数都是char **s,但是一个在main()中先分配,一个直接在getArray中分配,原因就在于此,getarray函数在main函数中先分配了内存,然后传递给它,虽然仍然是值传递,但是

s的元素是指针,getarray函数中在main函数分配的内存上完成了操作,所以当函数结束时,所有操作仍然保留下来。getarray1函数不同,它是在函数体内完整分配内存,然后施加操作的,相当于都在副本上,所有操作都不会

在函数结束后保留下来。

这是个人的一点理解,如有不对的地方还请指教。

c++用参数返回堆上的空间的更多相关文章

  1. C语言提高 (3) 第三天 二级指针的三种模型 栈上指针数组、栈上二维数组、堆上开辟空间

    1 作业讲解 指针间接操作的三个必要条件 两个变量 其中一个是指针 建立关联:用一个指针指向另一个地址 * 简述sizeof和strlen的区别 strlen求字符串长度,字符数组到’\0’就结束 s ...

  2. C++:在堆上创建对象,还是在栈上?

    这篇文章来自于一次讨论:http://www.devbean.net/2013/01/qt-study-road-2-model-view/#comment-17532.关于究竟是在堆上还是在栈上创建 ...

  3. 求你了,别再说Java对象都是在堆内存上分配空间的了!

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  4. 别再说Java对象都是在堆内存上分配空间的了!

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  5. JVM知识(一) 求你了,别再说Java对象都是在堆内存上分配空间的了!

    求你了,别再说Java对象都是在堆内存上分配空间的了! https://baijiahao.baidu.com/s?id=1661296872935371634&wfr=spider& ...

  6. Xamarin.Forms listview中的button按钮,实现带着参数返回上一级页面

    今天在做列表显示的时候遇到一个问题,就是在ListView中如何才能让一个button的按钮工作并且包含参数呢? 其实有点类似于rep里的控件无法起获取一样.在Xamarin中,当你button绑定事 ...

  7. 【Mybatis】MyBatis调用带有返回结果、output参数的存储过程上与ibatis的区别

    用过mybatis的应该都知道它是ibatis被Google收购后重新命名的一个工程,因此也做了大量升级.本文就来介绍下两者在调用存储过程上的一点区别,ibatis有一个专门的标签<proced ...

  8. python--函数的返回值、函数参数的使用、名称空间与作用域、函数嵌套、函数对象

    今天学习内容有函数的返回值.函数参数的使用.名称空间与作用域.函数嵌套. 下来我们一一查看. 函数的返回值 看几个栗子: def func(x): y=func() print(y) def foo( ...

  9. 只能在堆上生成的对象 VS. 只能在栈上生成的对象

    1. 只能在堆上 即禁止在栈上生成.如何实现? 当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象.如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存. 所以,只需 ...

随机推荐

  1. C#中的yield关键字

    迭代器,是一个连续的集合,出现多个yield return其实就是将这多个的yield return元素按照出现的顺序存储在迭代器的集合中而已.形如下面的形式: public class CityCo ...

  2. 常用软件的安装(windows/linux)

    1. matlab 2016b (两个 iso 文件)(windows) MATLAB R2016b 安装教程 2. clion 在 ubuntu 下的安装 ubuntu 14.04 Clion201 ...

  3. twemproxy

    twemproxy架构分析——剖析twemproxy代码前编   twemproxy背景 在业务量剧增的今天,单台高速缓存服务器已经无法满足业务的需求, 而相较于大容量SSD数据存储方案,缓存具备速度 ...

  4. QT 调用 DLL 方法(三种方法)

    Qt调用DLL方法一:使用Win32 API 在显式链接下,应用程序必须进行函数 调用以在运行时显式加载 DLL.为显式链接到 DLL,应用程序必须:? 调用 LoadLibrary(或相似的函 数) ...

  5. 史上最全最强SpringMVC详细示例实战教程【good】

    1)Spring MVC 在调用处理方法之前,在请求线程中自动的创建一个隐含的模型对象. 2)调用所有方法级的 标注了 @ModelAttribute 的方法,并将方法返回值添加到隐含的模型对象中. ...

  6. android网络开源框架volley(五岁以下儿童)——volley一些细节

    最近的一次volley整理出下一个.我以前没有再次遭遇了一些小问题,在该记录: 1.HttpUrlConnection DELETE 信息不能加入body问题:java.net.ProtocolExc ...

  7. 算法(algorithm)、模型(model)与框架(framework)

    模型对应的数学公式,公式中往往有待学习得到的参数,因此在进行训练或者学习时,首先初始化这部分参数(0 或标准正太分布): 学习之前的初始化:initial model: 学习完成之后的模型:final ...

  8. WPF Clip实现百叶窗

    原文:WPF Clip实现百叶窗 效果图; 后台代码: public MainWindow()         {             InitializeComponent();         ...

  9. React学习(1)——constructor

    constructor(props) { super(props); this.state = { orderNo: "001", wid: 6 }; } constructor: ...

  10. 多线程——继承Thread类别

    详细java此前使用多线程,让我们来看看下面的问题. 什么是多线程     简单的理解成:cpu"同一时候"运行多个任务,这就是多线程. (究其本质,当涉及到进程和线程的概念.上面 ...