之前我们讨论过类成员的组成,尤其是成员函数,我们知道了定义一个类的时候,我们往往定义了:构造函数,析构函数,其他函数,以及友元函数(友元函数不是必须的)。

同时,我们知道了这样一个事情:在定义一个对象的时候:构造函数会被调用;对象被销毁的时候:会调用析构函数。友元函数提供给我们访问成员的另一种方式。

但这其中,会产生更多的细节。通过一些具体的细节,我们来感受一些类中的行为:

类声明如下:

 # 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++入门之详细探讨类的一些行为的更多相关文章

  1. c++入门之再次探讨类属性

    精辟博文:https://blog.csdn.net/msdnwolaile/article/details/51923859(转载,仅供学习|!)

  2. c++ 入门之深入探讨拷贝函数和内存分配

    在c++入门之深入探讨类的一些行为时,说明了拷贝函数即复制构造函数运用于如下场景: 对象作为函数的参数,以值传递的方式传给函数. 对象作为函数的返回值,以值的方式从函数返回 使用一个对象给另一个对象初 ...

  3. JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式

    相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...

  4. Python编程从入门到实践笔记——类

    Python编程从入门到实践笔记——类 #coding=gbk #Python编程从入门到实践笔记——类 #9.1创建和使用类 #1.创建Dog类 class Dog():#类名首字母大写 " ...

  5. python入门学习:8.类

    python入门学习:8.类 关键点:类 8.1 创建和使用类8.2 使用类和实例8.3 继承8.4 导入类 8.1 创建和使用类   面向对象编程是最有效的软件编写方法之一.在面向对象编程中,你编写 ...

  6. discuz插件开发新手入门 超详细

    作为一个新手,目前也是刚刚玩转discuz的插件功能,好东西不敢独享,就拿出来大家一起分享入门的过程.现在网上很多关于discuz的插件教程都是很简单的教程,原因可能是这个东西是商业化的东西,本着分享 ...

  7. SpringBoot入门最详细教程

    monkey01 关注 2017.08.08 13:36* 字数 1479 阅读 34248评论 0喜欢 15 网上有很多springboot的入门教程,自己也因为项目要使用springboot,所以 ...

  8. SpringMVC札集(03)——基于注解的SpringMVC入门完整详细示例

    自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onL ...

  9. Python 3 快速入门 3 —— 模块与类

    本文假设你已经有一门面向对象编程语言基础,如Java等,且希望快速了解并使用Python语言.本文对重点语法和数据结构以及用法进行详细说明,同时对一些难以理解的点进行了图解,以便大家快速入门.一些较偏 ...

随机推荐

  1. 安装Window 10系统------计算机经验

    为什么这次安装window10系统呢?不是和window7系统的安装方法一样么?如果你是这样的想的话,是不完全对的,因为window10系统的安装有些繁杂,需要耐心.下面我就准备了官方原版的windo ...

  2. shell脚本:通过域名获取证书的过期时间

    需要两个文件,一个用于存储域名信息,另一个是检测脚本 注意:这两个文件是在一个目录下 domain_ssl.info [存储域名信息] [root@mini05 ]# cat domain_ssl.i ...

  3. June 7. 2018 Week 23rd Thursday

    Half is worse than none at all. 一知半解比一无所知更痛苦. From Westworld. If we go looking for the truth, get th ...

  4. Welcom to Swift

    1.第一个程序 import Foundation println(“hello world”) 2.常用数据类型 int/UInt/Double/Float/Bool/String/Array/Di ...

  5. Angular四大核心特性

    Angular四大核心特性 Angular四大核心特性理论概述 MVC模式:它目的是为了分离视图.模型和控制器而设计出来的:其中数据模型用来储存数据,视图用来向用户展示应用程序,控制器充当模型和视图之 ...

  6. margin-left:-20px

    对于负的margin值,设置其作用时要和position:absolute;一起使用

  7. UVA215-Spreadsheet Calculator(模拟+拓扑排序)

    Problem UVA215-Spreadsheet Calculator Accept:401  Submit:2013 Time Limit: 3000 mSec Problem Descript ...

  8. WIN10 企业版 LTSC 激活

    Windows10 企业版长期服务支持分支版本: Windows 10 LTSC 2019 WIN+X 选POWERSHELL 管理员版本 输入以下 slmgr -ipk M7XTQ-FN8P6-TT ...

  9. hyperledge工具-cryptogen

    参考:http://baijiahao.baidu.com/s?id=1596614770784685300&wfr=spider&for=pc cryptogen是Hyperledg ...

  10. day25 Python四个可以实现自省的函数,反射

    python面向对象中的反射:通过字符串的形式操作对象相关的属性.python中的一切事物都是对象(都可以使用反射) 四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象) ...