c++入门之详细探讨类的一些行为
之前我们讨论过类成员的组成,尤其是成员函数,我们知道了定义一个类的时候,我们往往定义了:构造函数,析构函数,其他函数,以及友元函数(友元函数不是必须的)。
同时,我们知道了这样一个事情:在定义一个对象的时候:构造函数会被调用;对象被销毁的时候:会调用析构函数。友元函数提供给我们访问成员的另一种方式。
但这其中,会产生更多的细节。通过一些具体的细节,我们来感受一些类中的行为:
类声明如下:
# include "iostream"
# ifndef STRNGBAD_H_
# define STRNGBAD_H_
class StringBad
{
private:
char *str;
int len;
static int num_strings; //= 0;//可见除了const 量之外,类内部成员是不能在内部赋初值的
public:
StringBad(const char * s);
StringBad();
~StringBad(); friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
};
# endif
上述这个类声明中:
和通常一样:包含了构造函数,析构函数,友元函数;但这里需要强调的是 静态成员变量:num_strings。当我们使用StringBad类生成A,B,C....诸多类对象的时候,A.str,B.str,C.str....这些变量本质上地址是不同的。但是A.num_strings,B.num_strings,A.num_strings,地址是相同的。即:静态成员是属于类,而不是属于对象的。
我们看看这个类的定义:
# include "cstring"
# include "strngbad.h" using std::cout; int StringBad::num_strings = ; StringBad::StringBad(const char*s)
{
len = std::strlen(s);
str = new char[len + ];
std::strcpy(str, s);
num_strings++;
cout << num_strings << ": \"" << str << "\" object created\n";
} StringBad::StringBad()
{
len = ;
str = new char[];
std::strcpy(str, "C++");
num_strings++;
cout << num_strings << ": \"" << str << "\" default created\n";
} StringBad::~StringBad()
{
cout << "\"" << str << "\" object created. ";
--num_strings;
cout << num_strings << "left\n";
delete[] str;
} std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;//注意这里打印的是 str,如果不加str呢
return os;
}
关于这个类定义:我们注意这么几点:第6行进行了静态变量的初始化。注意,对num_strings 进行初始化的时候,StringBad::num_strings表明num_strings成员属于StringBad
问题:可以在类声明中进行初始化吗?
答:不可以,类声明只是说明需要为什么样的类型分配多少空间,但并不真正的分配空间,因此不能在声明中初始化(const除外)。
从这个类定义文件可以看出:无论是描述类成员函数,还是类成员变量.我们都要在前面描述其作用域!
new 和delete在动态内存分配中是十分重要的。但是要注意:在构造函数中使用了new[],在析构函数中必有delete[].
继续看调用文件:
# include "iostream"
using std::cout;
# include "strngbad.h" void callme1(StringBad &);
void callme2(StringBad); int main()
{
using std::endl;
{
cout << "Starting an inner block.\n";
StringBad headline1("hello world!");
StringBad headline2("learning forever!");
StringBad sports("i love trvaling!");
cout << "headline1" << headline1 << endl;
cout << "headline12"<< headline2 << endl;
cout << "sports" << sports << endl;
callme1(headline1);
cout << "headline1" << headline1 << endl;
callme2(headline2);
cout << "headline2" << headline2 << endl;
cout << "Initialize one object to another:\n";
StringBad sailor = sports;
cout << "sailor: " << sailor << endl;
}
cout << "End of main()\n";
system("pause");
return ;
} void callme1(StringBad & rsb)
{
cout << "String passed by reference:\n";
cout << " \n" << rsb << "\"\n";
} void callme2(StringBad sb)
{
cout << "String passed by rvalue:\n";
cout << " \n" << sb << "\"\n";
}
在此,给出第19行代码和第21行代码的一些细节:
第19行代码,传递参数的行为是引用传递,21行为:值传递。
再次强调:值传递 和 值返回 函数的两个本质特征:1. 进行值传递的时候,复制实参的副本,使得形参为实参的副本;2. 值返回的时候,复制返回值的副本(并销毁该变量(因为栈中的变量生存周期为函数调用期)),对副本进行后续操作。
因此第21行的代码:在进行按值传递的时候,首先创建一个对象的副本。但在创建对象副本的时候,首先会调用构造函数,但是这个构造函数是谁呢?是之前类定义中的构造函数吗?如果是,显然这个创建副本的形式应该为:()或者(cha*),但问题是:这里是吗???显然不是,那是什么呢???其实为:B=A,即将已知的对象A赋值给临时对象B,这就如同第24行的代码。那么这样的初始化方式,调用哪个构造函数呢???其实,是一种叫做赋值构造函数,专门应对这种赋值初始化的操作。很明显,之前的声明和定义中并没有赋值构造函数。那么这个赋值构造函数只能由编译器产生。
终于知道为何开始要求我们采用()或者{}的初始化方式,而不是"="的初始化方式。因为通常的我们可能并没有显示的定义自己的赋值构造函数,这个时候由编译器产生一个这样的函数,可能会存在一定的风险。但如果我们不可避免会采用到=或者值传递创建函数的形式,我们应该显示的定义自己的赋值构造函数,使得我们的程序风险更低。(实际上,通常的定义一个显示赋值构造函数更保险)
总结:按值传递和按值返回对象的时候,都将调用赋值构造函数!2构造函数中创建一个显示赋值构造函数通常更保险
我们在此再次声明:“hello World!”表示一个地址而不是字符串。当我们定义:p = “hello Word!”,假如我们定义:q = p,那么:是否是将字符串P复制了一遍,并存到了q的地址呢?并不是,而是:p,q都指向了“hello world!”,而这个时候,“hello world! ”其实看起来更像地址。
c++入门之详细探讨类的一些行为的更多相关文章
- c++入门之再次探讨类属性
精辟博文:https://blog.csdn.net/msdnwolaile/article/details/51923859(转载,仅供学习|!)
- c++ 入门之深入探讨拷贝函数和内存分配
在c++入门之深入探讨类的一些行为时,说明了拷贝函数即复制构造函数运用于如下场景: 对象作为函数的参数,以值传递的方式传给函数. 对象作为函数的返回值,以值的方式从函数返回 使用一个对象给另一个对象初 ...
- JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式
相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...
- Python编程从入门到实践笔记——类
Python编程从入门到实践笔记——类 #coding=gbk #Python编程从入门到实践笔记——类 #9.1创建和使用类 #1.创建Dog类 class Dog():#类名首字母大写 " ...
- python入门学习:8.类
python入门学习:8.类 关键点:类 8.1 创建和使用类8.2 使用类和实例8.3 继承8.4 导入类 8.1 创建和使用类 面向对象编程是最有效的软件编写方法之一.在面向对象编程中,你编写 ...
- discuz插件开发新手入门 超详细
作为一个新手,目前也是刚刚玩转discuz的插件功能,好东西不敢独享,就拿出来大家一起分享入门的过程.现在网上很多关于discuz的插件教程都是很简单的教程,原因可能是这个东西是商业化的东西,本着分享 ...
- SpringBoot入门最详细教程
monkey01 关注 2017.08.08 13:36* 字数 1479 阅读 34248评论 0喜欢 15 网上有很多springboot的入门教程,自己也因为项目要使用springboot,所以 ...
- SpringMVC札集(03)——基于注解的SpringMVC入门完整详细示例
自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onL ...
- Python 3 快速入门 3 —— 模块与类
本文假设你已经有一门面向对象编程语言基础,如Java等,且希望快速了解并使用Python语言.本文对重点语法和数据结构以及用法进行详细说明,同时对一些难以理解的点进行了图解,以便大家快速入门.一些较偏 ...
随机推荐
- EOS智能合约开发(四):智能合约部署及调试(附编程示例)
EOS智能合约开发(一):EOS环境搭建和创建节点 EOS智能合约开发(二):EOS创建和管理钱包 EOS智能合约开发(三):EOS创建和管理账号 部署智能合约的示例代码如下: $ cleos set ...
- MacOS远程Windows提示:远程桌面连接无法验证您希望连接的计算机的身份
解决方法: 1.在Windows端,运行输入 “gpedit.msc”,打开本地组策略编辑器 2.依次打开[计算机配置]→[管理模板]→[windows组件]→[远程桌面服务]→[远程桌面会话主机]→ ...
- puppet 横向扩展(一)
目录 1. 概述 2. 实验环境 3. 实验步骤 3.1. 创建puppetmaster的rack环境 3.2. 配置文件设置 3.3. 补充说明 3.4. 测试配置结果 3.4.1. 默认的负载均衡 ...
- "敏捷革命"读书笔记
最近看可一本书 书名叫<敏捷革命>外国著作中文翻译 本来想自己总结读后感但是本书后面都有本章的总结,所以下面都已摘抄为主,以备之后快速浏览 第一章 世界的运作方式已经打破 规划是有用的,而 ...
- Spring的AOP基于AspectJ的注解方式开发1
参考自黑马培训机构 创建项目,引入jar包 编写目标类,切面类并完成配置 package spring.day2_aop2; /* * 编写目标类 */ public class OrderDao { ...
- 合并两个有序数组的golang实现
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组. 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n. ...
- 注册mySQL到JDBC驱动程序方法浅谈
一.注册方法(4种) 1)服务提供者框架: 符合JDBC 4.0规范的驱动程序包含了一个文件META-INF/services/java.sql.Driver,在这个文件中提供了JDBC驱动实现的类名 ...
- 使用 vagrant新建Linux虚拟机
准备工作 1.下载软件 2.安装软件 2.1 安装VirtualBox-5.1.34-121010-Win.exe 2.2 安装vagrant_2.0.3_x86_64.msi 3.新建 执行指令D: ...
- SDOI2016 R1做题笔记
SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...
- Oracle的条件in中包含NULL时的处理
我们在写SQL时经常会用到in条件,如果in包含的值都是非NULL值,那么没有特殊的,但是如果in中的值包含null值(比如in后面跟一个子查询,子查询返回的结果有NULL值),Oracle又会怎么处 ...