深入理解指针作为函数形参

楼主刚从java转到c++,被这个指针搞得头疼,今天晚上碰到这个问题,想了好久,翻书也翻了很久,终于弄明白这其中的奥秘。

首先我们得明白c++中的函数参数传递一个重要规则X(姑且命名X,方便下面叙述)是:

每次调用一个函数,都会重新建立一个形参(类似于临时变量),传入的实参作为初始化值拷贝给形参,一旦实参的值拷贝给形参后,形参和实参其实是两个不同的对象了(c++Prime原话,啃书!!!),就互不干涉了。这句话真的太重要!!!

接下来,通过一个简单的例子,来演示一下指针作为函数形参的两个容易混淆的问题。

1)可以改变指针指向的内容:

void test(int* s) {//指针作为形参

	//s = new int(2);//p指向2这块内存空间
	*s = 20;//指针指向的内容修改
    
}
int main() {
	int a = 1000;
	int* p = &a;
	cout << "调用前p的地址" << p << endl;//调用前p的地址004FFEC0

	cout << "调用前p指向的值" << *p << endl;//调用前p指向的值1000
	test(p);
	cout << "调用后p的地址" << p << endl;//调用后p的地址004FFEC0
	cout << "调用后p指向的值" << *p << endl;//调用后p指向的值20
}

如上面代码所示,main函数中的指针p原本是指向a的内存,且存的数是1000,输出为:

“调用前p的地址004FFEC0”

“调用前p指向的值1000”

 根据X规则,test()函数在调用时,形参s生成临时变量int* s,实参p用于s的初始化,所以s等于p,也可以理解s和p指向同一块内存,故通过*s对指针指向的内容进行修改:*s= 20,也就是等同于*p=20!所以调用后输出:

“调用前p的地址004FFEC0”

“调用前p指向的值20”

即地址没变,但是值修改了。

由这个例子其实可以看出来,地址没改变,改变了指针指向的内容

2) 不可以改变指针的地址:

void test(int* s) {//指针作为形参
	s = new int(2);//p指向2这块内存空间
    cout << "调用后s的地址" << s << endl;//调用后s的地址01034FC8
	//*s = 20;//指针指向的内容修改
}
int main() {
	int a = 1000;
	int* p = &a;
	cout << "调用前p的地址" << p << endl;//调用前p的地址010FF7BC

	cout << "调用前p指向的值" << *p << endl;//调用前p指向的值1000
	test(p);
	cout << "调用后p的地址" << p << endl;//调用后p的地址010FF7BC

	cout << "调用后p指向的值" << *p << endl;//调用后p指向的值1000
}

这个例子中我让test函数对指针的指向进行改变。main中的p仍然指向a,在调用后,根据输出语句的地址发现:指向的还是a,地址未发生改变。但是s的地址和p的地址不一样了,因为“一旦实参的值拷贝给形参后,形参和实参其实是两个不同的对象了,就互不干涉了。”注意!这个s和p不是一个东西了。

3)针对1)和2)的思考

为什么指针作为形参可以改变指针指向的内容,却不可以改变指针的指向?我觉得一定得从函数的参数传递本质着手,函数参数传递的规则是实参是形参的初始化,一旦实参的值拷贝给形参后,形参和实参其实是两个不同的对象了。这个一定好好理解一下!基于这个规则:

当一个函数将指针作为形参时,我们给这个形参传入一个实参(指针(非引用类型)),这个实参的值究竟是什么?其实是指针的地址值,这个地址值传给形参后,后续形参怎么折腾,跟这个实参无关的。如:1)中只是*s = 20,这个s其实就是p,因为s没有对指针指向进行修改(可以理解成s和p指向同一个内存,那么s修改了指针指向的内存保存的值,p指向的值当然也得变)。2)中 s = new int(2);就是把s指向了另一个内存,虽然test函数中s的地址变了;但是跟p没有半毛钱关系,所以main中输出的两个p的地址还是一样的。归根结底还是因为“一旦实参的值拷贝给形参后,形参和实参其实是两个不同的对象了,就互不干涉了”。 这句话理解了, 就啥都明白了。

形象一点的说就是:

我把我家的钥匙(实参)拷贝了一份给你(形参),你可以用拷贝的钥匙开我家的门,拿我家的东西,完全没问题,但是你非要去开锁店把我家的钥匙改造成另一个样子,即使可能你可以用这个改造的钥匙去开别人家的门(改变指向),但是你根本不会影响我自己手上这把钥匙继续开我家的门(不改变实参的指向)。

第一次写帖子,只是想分享今天学到的心得。可能总结的不好,不喜勿喷。如果有错的地方,希望大佬们给与指正!感谢!

  • 40
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
visualC++2010入门经典源代码 第1章 使用visual c++ 2010编程 1.1 .net framework 1 1.2 clr 2 1.3 编写c++应用程序 3 1.4 学习windows编程 4 1.4.1 学习c++ 4 1.4.2 c++标准 5 1.4.3 属性 5 1.4.4 控制台应用程序 5 1.4.5 windows编程概念 6 1.5 集成开发环境简介 7 1.5.1 编辑器 8 1.5.2 编译器 8 1.5.3 链接器 8 1.5.4 库 8 1.6 使用ide 8 1.6.1 工具栏选项 9 1.6.2 可停靠的工具栏 10 1.6.3 文档 11 1.6.4 项目和解决方案 11 1.6.5 设置visual c++ 2010的选项 23 1.6.6 创建和执行windows应用程序 23 1.6.7 创建windows forms应用程序 26 1.7 小结 27 1.8 本章主要内容 28 第2章 数据、变量和计算 29 2.1 c++程序结构 29 2.1.1 main()函数 36 2.1.2 程序语句 36 2.1.3 空白 38 2.1.4 语句块 38 2.1.5 自动生成的控制台程序 39 2.2 定义变量 40 2.2.1 命名变量 40 2.2.2 声明变量 41 2.2.3 变量的初始值 42 2.3 基本数据类型 42 2.3.1 整型变量 43 2.3.2 字符数据类型 44 2.3.3 整型修饰符 45 2.3.4 布尔类型 46 2.3.5 浮点类型 46 2.3.6 字面值 47 2.3.7 定义数据类型的同义词 48 2.3.8 具有特定值集的变量 49 2.4 基本的输入/输出操作 50 2.4.1 从键盘输入 50 2.4.2 到命令行的输出 50 2.4.3 格式化输出 51 2.4.4 转义序列 52 2.5 c++中的计算 54 2.5.1 赋值语句 54 2.5.2 算术运算 55 2.5.3 计算余数 59 2.5.4 修改变量 60 2.5.5 增量和减量运算符 60 2.5.6 计算的顺序 63 2.6 类型转换和类型强制转换 64 2.6.1 赋值语句中的类型转换 65 2.6.2 显式类型转换 65 2.6.3 老式的类型强制转换 66 2.7 auto关键字 66 2.8 查看类型 67 2.9 按位运算符 67 2.9.1 按位and运算符 68 2.9.2 按位or运算符 69 2.9.3 按位eor运算符 71 2.9.4 按位not运算符 71 2.9.5 移位运算符 71 2.10 lvalue和rvalue 73 2.11 了解存储时间和作用域 74 2.11.1 自动变量 74 2.11.2 决定变量声明的位置 76 2.11.3 全局变量 77 2.11.4 静态变量 80 2.12 名称空间 80 2.12.1 声明名称空间 81 2.12.2 多个名称空间 82 2.13 c++/cli编程 84 2.13.1 c++/cli特有的基本数据类型 84 2.13.2 命令行上的c++/cli输出 87 2.13.3 c++/cli特有的功能—— 格式化输出 88 2.13.4 c++/cli的键盘输入 91 2.13.5 使用safe_cast 92 2.13.6 c++/cli枚举 92 2.14 查看c++/cli类型 96 2.15 小结 97 2.16 练习 97 2.17 本章主要内容 98 第3章 判断和循环 101 3.1 比较数据值 101 3.1.1 if语句 102 3.1.2 嵌套的if语句 104 3.1.3 嵌套的if-else语句 107 3.1.4 逻辑运算符和表达式 109 3.1.5 条件运算符 112 3.1.6 switch语句 113 3.1.7 无条件转移 116 3.2 重复执行语句块 117 3.2.1 循环的概念 117 3.2.2 for循环的变体 119 3.2.3 while循环 126 3.2.4 do-while循环 128 3.2.5 嵌套的循环 129 3.3 c++/cli编程 132 3.4 小结 137 3.5 练习 138 3.6 本章主要内容 138 第4章 数组、字符串和指针 139 4.1 处理多个相同类型的数据值 139 4.1.1 数组 140 4.1.2 声明数组 140 4.1.3 初始化数组 143 4.1.4 字符数组和字符串处理 144 4.1.5 多维数组 147 4.2 间接数据访问 150 4.2.1 指针的概念 150 4.2.2 声明指针 150 4.2.3 使用指针 152 4.2.4 初始化指针 152 4.2.5 sizeof操作符 158 4.2.6 常量指针和指向常量的指针 159 4.2.7 指针和数组 161 4.3 动态内存分配 168 4.3.1 堆的别名—— 空闲存储器 168 4.3.2 new和delete操作符 168 4.3.3 为数组动态分配内存 169 4.3.4 多维数组的动态分配 171 4.4 使用引用 172 4.4.1 引用的概念 172 4.4.2 声明并初始化lvalue引用 172 4.4.3 声明并初始化rvalue引用 173 4.5 字符串的本地c++函数 174 4.5.1 查找以空字符结尾的字符串的长度 174 4.5.2 连接以空字符结尾的字符串 174 4.5.3 复制以空字符结尾的字符串 176 4.5.4 比较以空字符结尾的字符串 177 4.5.5 搜索以空字符结尾的字符串 177 4.6 c++/cli编程 179 4.6.1 跟踪句柄 180 4.6.2 clr数组 181 4.6.3 字符串 195 4.6.4 跟踪引用 203 4.6.5 内部指针 204 4.7 小结 206 4.8 练习 206 4.9 本章主要内容 207 第5章 程序结构(1) 209 5.1 理解函数 209 5.1.1 需要函数的原因 210 5.1.2 函数的结构 210 5.1.3 使用函数 213 5.2 给函数传递实参 216 5.2.1 按值传递机制 216 5.2.2 给函数传递指针实参 217 5.2.3 给函数传递数组 219 5.2.4 给函数传递引用实参 222 5.2.5 使用const修饰符 224 5.2.6 rvalue引用形参 225 5.2.7 main()函数的实参 227 5.2.8 接受数量不定的函数实参 229 5.3 从函数返回值 231 5.3.1 返回指针 231 5.3.2 返回引用 233 5.3.3 函数中的静态变量 236 5.4 递归函数调用 238 5.5 c++/cli编程 240 5.5.1 接受数量可变实参的函数 241 5.5.2 main( )的实参 242 5.6 小结 243 5.7 练习 243 5.8 本章主要内容 244 第6章 程序结构(2) 245 6.1 函数指针 245 6.1.1 声明函数指针 246 6.1.2 函数指针作为实参 249 6.1.3 函数指针的数组 250 6.2 初始化函数形参 250 6.3 异常 252 6.3.1 抛出异常 253 6.3.2 捕获异常 254 6.3.3 mfc中的异常处理 255 6.4 处理内存分配错误 256 6.5 函数重载 257 6.5.1 函数重载的概念 258 6.5.2 引用类型和重载选择 260 6.5.3 何时重载函数 260 6.6 函数模板 261 6.7 使用decltype操作符 263 6.8 使用函数的示例 265 6.8.1 实现计算器 265 6.8.2 从字符串中删除空格 268 6.8.3 计算表达式的值 268 6.8.4 获得项值 270 6.8.5 分析数 271 6.8.6 整合程序 274 6.8.7 扩展程序 275 6.8.8 提取子字符串 277 6.8.9 运行修改过的程序 279 6.9 c++/cli编程 279 6.9.1 理解泛型函数 280 6.9.2 clr版本的计算器程序 285 6.10 小结 290 6.11 练习 291 6.12 本章主要内容 292 第7章 自定义数据类型 293 7.1 c++中的结构 293 7.1.1 结构的概念 294 7.1.2 定义结构 294 7.1.3 初始化结构 294 7.1.4 访问结构的成员 295 7.1.5 伴随结构的智能感知帮助 298 7.1.6 rect结构 299 7.1.7 使用指针处理结构 300 7.2 数据类型、对象、类和实例 301 7.2.1 类的起源 303 7.2.2 类的操作 303 7.2.3 术语 303 7.3 理解类 304 7.3.1 定义类 304 7.3.2 声明类的对象 305 7.3.3 访问类的数据成员 305 7.3.4 类的成员函数 307 7.3.5 成员函数定义的位置 309 7.3.6 内联函数 309 7.4 类构造函数 310 7.4.1 构造函数的概念 311 7.4.2 默认的构造函数 312 7.4.3 在类定义中指定默认的形参值 314 7.4.4 在构造函数中使用初始化列表 316 7.4.5 声明显式的构造函数 317 7.5 类的私有成员 318 7.5.1 访问私有类成员 320 7.5.2 类的友元函数 321 7.5.3 默认复制构造函数 323 7.6 this指针 325 7.7 类的const对象 327 7.7.1 类的const成员函数 327 7.7.2 类外部的成员函数定义 328 7.8 类对象的数组 329 7.9 类的静态成员 331 7.9.1 类的静态数据成员 331 7.9.2 类的静态函数成员 334 7.10 类对象的指针和引用 334 7.10.1 类对象的指针 334 7.10.2 类对象的引用 337 7.11 c++/cli编程 338 7.11.1 定义值类类型 339 7.11.2 定义引用类类型 344 7.11.3 定义引用类类型的复制构造函数 346 7.11.4 类属性 346 7.11.5 initonly字段 358 7.11.6 静态构造函数 360 7.12 小结 360 7.13 练习 360 7.14 本章主要内容 361 第8章 深入理解类 363 8.1 类析构函数 363 8.1.1 析构函数的概念 363 8.1.2 默认的析构函数 364 8.1.3 析构函数与动态内存分配 366 8.2 实现复制构造函数 369 8.3 在变量之间共享内存 370 8.3.1 定义联合 371 8.3.2 匿名联合 372 8.3.3 类和结构中的联合 372 8.4 运算符重载 373 …… 第9章 类继承和虚函数 第10章 标准模板库 第11章 调试技术 第12章 windows编程的概念 第13章 多核编程 第14章 使用mfc编写windows程序 第15章 处理菜单和工具栏 第16章 在窗口中绘图 第17章 创建文档和改进视图 第18章 使用对话框和控件 第19章 存储和打印文档 第20章 编写自己的dll

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值