C++基础学习笔记----第十四课(new和malloc的区别、单例模式等深入)
本节主要讲new关键字和malloc函数的差别,编译器对构造函数调用的实质,单例模式的实现等。
new和malloc的差别
1.malloc和free是C语言的库函数,以字节为单位申请堆空间。new和delete是C++的关键字,以类型为单位申请堆空间。malloc和free单纯的对内存申请和释放,对于类类型new和delete还负责构造函数和析构函数的调用。
2.malloc只是单纯的申请一块内存空间,并不负责调用构造函数。构造函数的本质是用来初始化对象的,而malloc不具备初始化的功能,所以不具备调用构造函数来对申请的对象进行初始化。
3.delete关键字不单单是将内存归还给系统,还调用析构函数来销毁对象。但是free函数只是单纯的释放内存,并不能够调用析构函数来销毁对象,所以在涉及到类类型的时候free函数的释放内存可能会造成内存泄露。
4.在针对普通的函数数组和变量的时候,使用malloc和new不会有差别(存在差别可能是执行效率上,但是这个涉及到C++内部的机制,暂时不讨论),但是涉及到类类型的时候new和malloc以及free和delete可能会产生完全不同的效果。
基本例程如下:
#include <stdio.h>
#include <stdlib.h> class A
{
private:
int a;
int b;
public:
A()
{
printf("This is A()!\n");
}
~A()
{
printf("This is ~A()!\n");
}
void print()
{
printf("123\n");
}
}; int main()
{
int *a = new int; /*使用强制类型转换将申请的内存转换为int*类型*/
int *b = reinterpret_cast<int*>(malloc(sizeof(int))); *a = 9;
*b = 8; printf ("*a = %d\n",*a);
printf ("*b = %d\n",*b); free(b);
delete a; A* c = new A;
A* d = reinterpret_cast<A*>(malloc(sizeof(A))); c->print();
d->print(); delete c;
//free(c); free(d); return 0;
}
程序打印结果如下:
通过打印结果可以发现:当使用new和malloc为普通变量申请内存空间的时候程序执行的结果没有差别,当使用new和malloc分别申请类的对象的空间时候,malloc和free这两个函数都不调用构造和析构函数。同时,使用new关键字来申请的空间,可以使用free函数来释放,当空间中的内容是普通变量的时候没有差别,当是类类型的内存的时也将不再调用析构函数。
编译器对构造函数的调用及explicit关键字
假定类A,如果类中定义了普通构造函数和拷贝构造函数,在其他函数中定义三个该类的对象,代码如下:
A a1(1);
A a2 = 2;
A a3 = A(3);
整体程序实现代码如下:
#include <stdio.h> class A
{
private:
int a;
int b;
public:
A(int i)
{
printf ("A(int)\n");
} A(const A& aj)
{
printf("A(const)\n");
} ~A()
{
printf ("~A()\n");
}
}; void func()
{
A a1(1);
A a2 = 2;
A a3 = A(3);
} int main()
{
func();
return 0;
}
程序打印结果如下:
通过上面的打印结果可以发现,在编译器中,三种类的初始方法都是调用了普通构造函数,同时在调用结束调用了三次构造函数,并没有涉及到拷贝构造函数的调用,这是.........现代C++编译器的优化。
在古代C++编译器中,A a1(1);这种格式调用析构函数和调用普通的函数没有差别,因为函数的调用和参数都是符合类中定义的构造函数的。A a1 = 2;这种格式调用析构函数可以发现,这里的2是一个字面量且为整形,但是等号的左面是一个A类型的对象,所以等号两面的类型并不匹配。编译器的在默认的情况下自动调用拷贝构造函数。编译器将
A a1 = 2;转化为A a1 = A(2);这里是直接调用A的构造函数A(int i),所以这里将会产生一个临时对象,也就是转化成了使用一个类的对象去初始化一个新定义的这个类的对象,这是合法的。最后调用拷贝构造函数A(const A& aj)使用临时对象进行初始化。但是现在的C++编译器其实已经将上面的步骤省略掉了,现代C++编译器直接是将 A a1 = 2转化成为A a1(2);这样大大的节省了程序的编译运行时间。
C++编译器调用构造函数的基本原则如下图:
explicit关键字
explicit的作用是剥夺C++编译器对构造函数的主动的调用尝试。
基本例程如下:
#include <stdio.h> class A
{
private:
int a;
int b;
public:
explicit A()
{
printf("This is A()!\n");
} explicit A(int i)
{
printf("This is A(int)\n");
} ~A()
{
printf("This is ~A()!\n");
}
void print()
{
printf("123\n");
}
}; int main()
{
A a1;
//a1.print(); A a2(1);
//A a3 = 2; return 0;
}
程序打印结果如下:
通过上面的结果可以发现:定义对象a1的代码如下:
A a1;
这里程序依然正常调用了经过explicit修饰的构造函数,因为这是默认的初始化,并不是C++主动调用的,同样A a2(1);这个初始化类的对象也正常调用了构造函数。但是
A a3 = 2;
这条初始化类的对象的函数将不会编译通过,因为这里编译器在编译的过程中将会 主动调用经过explicit修饰的构造函数,所以程序将会编译出错。
单例模式
单例模式是C++语言的一种设计模式,我觉得面向对象的语言都存在设计模式的问题,悄悄的想起从买了就没看过的大话设计模式了~
在实际编写程序过程中,有些场景要求一个类智能有一个对象存在于系统之中,称为单例模式。例如:一个汽车对象只能有一个发动机对象。
基本例程如下:
#include <cstdlib>
#include <iostream> using namespace std; class Singleton
{
private:
/*定义静态成员变量*/
static Singleton* cInstance; Singleton()
{
}
public:
/*实现一个静态成员函数,这个静态成员函数可以直接访问上面定义的静态成员变量*/
static Singleton* GetInstance()
{
if( cInstance == NULL )
{
cout<<"new Singleton()"<<endl;
/*这里是可以调用这个类中的普通成员函数的,无论是否是private还是public的*/
cInstance = new Singleton();
} /*这里返回的是指针*/
return cInstance;
} void print()
{
cout<<"I'm Singleton!"<<endl;
}
}; /*在类的外部定义类的静态成员变量*/
Singleton* Singleton::cInstance = NULL; void func()
{
/*
定义一个类的指针,使这个指针指向Singleton类的函数的返回值,实际上就是完成了一个对象的建立
这里s所指向的对象与cInstance所指向的对象是完全相同的
*/
Singleton* s = Singleton::GetInstance();
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance(); cout<<s<<" "<<s1<<" "<<s2<<endl; s->print();
} int main(int argc, char *argv[])
{
func(); return EXIT_SUCCESS;
}
程序打印结果如下图:
通过程序打印结果可以发现,无论在函数中申请调用几次类的对象,包括s,s1,s2,但是它们都指向同一个地址,实现了单例模式的设计。这里应该存在一块非法空间,程序在new之后并没有进行delete.
状态函数和无状态函数以及斐波拉契数列的实现
基本概念:
无状态函数的调用结果只与实参值相关。状态函数的调用结果不仅仅与实参值相关还与之前的函数调用有关。
例程实现:
#include <stdio.h> int fib1(int i)
{
int a = 0;
int b = 1;
int ret = b; while(i >= 1)
{
ret = a + b;
a = b;
b = ret;
i--;
} return ret;
} int fib2()
{
static int a = 0;
static int b = 1; int ret = b;
int g = b; b = a + b;
a = g; return ret;
} class A
{
private:
int a;
int b;
public:
A()
{
a = 0;
b = 1;
}
int fib3()
{
int ret = b;
int g = b; b = a + b;
a = g; return ret;
}
}; int main()
{
A a; for (int i = 0; i < 5; i++)
{
printf("%d\n",fib1(i));
} /*for (int i = 0; i < 5; i++)
{
printf("%d\n",fib1(i));
}*/ printf ("\n"); for (int j = 0; j < 5; j++)
{
printf("%d\n",fib2());
} /*for (int j = 0; j < 5; j++)
{
printf("%d\n",fib2());
}*/ printf("\n"); for (int k = 0; k < 5; k++)
{
printf("%d\n",a.fib3());
} /*A a1; for (int m = 0; m < 5; m++)
{
printf("ea.fib3() %d\n",a1.fib3());
}*/ return 0;
}
程序打印结果如下:
通过上面的打印结果可以发现函数fib1()、fib2()和对象a调用的函数fib3()打印结果都相同, 其中fib1()是以无状态函数的方式实现的,求解斐波拉切数列的每一项时都会做重复循环,时间复杂度为O(n),fib2()是以状态函数方式实现的,每一次调用就可以得到数列当前项的数值,时间复杂度为O(1),但是如果想要再求前面第几个数的值将无法实现。通过类A定义的对象a调用成员函数fib3(),时间复杂度是O(1),同时如果想要求前面数的值,那么可以重新定义一个对象即可。
C++基础学习笔记----第十四课(new和malloc的区别、单例模式等深入)的更多相关文章
- Java基础学习笔记二十四 MySQL安装图解
.MYSQL的安装 1.打开下载的mysql安装文件mysql-5.5.27-win32.zip,双击解压缩,运行“setup.exe”. 2.选择安装类型,有“Typical(默认)”.“Compl ...
- VSTO学习笔记(十四)Excel数据透视表与PowerPivot
原文:VSTO学习笔记(十四)Excel数据透视表与PowerPivot 近期公司内部在做一种通用查询报表,方便人力资源分析.统计数据.由于之前公司系统中有一个类似的查询使用Excel数据透视表完成的 ...
- Python学习笔记(十四)
Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
- python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法
python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法window安装redis,下载Redis的压缩包https://git ...
- (C/C++学习笔记) 二十四. 知识补充
二十四. 知识补充 ● 子类调用父类构造函数 ※ 为什么子类要调用父类的构造函数? 因为子类继承父类,会继承到父类中的数据,所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程. ...
- 如鹏网学习笔记(十四)ASP.NET
Asp.net笔记 一.Socket类 进行网络编程的类,可以在两台计算机之间进行网络通讯 过程: 向服务器发送指令: GET /index.html HTTP/1.1 Host:127.0.0.1: ...
- 《机器学习实战》学习笔记第十四章 —— 利用SVD简化数据
相关博客: 吴恩达机器学习笔记(八) —— 降维与主成分分析法(PCA) <机器学习实战>学习笔记第十三章 —— 利用PCA来简化数据 奇异值分解(SVD)原理与在降维中的应用 机器学习( ...
- Android学习笔记(十四)——自定义广播
//此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 我们除了可以通过广播接收器来接收系统广播, 还可以在应用程序中发送自定义的广播.下面我们来分别试一试发送自定义 ...
- Dynamic CRM 2013学习笔记(十四)复制/克隆记录
经常有这样的需求,一个单据上有太多要填写的内容,有时还关联多个子单据,客户不想一个一个地填写,他们想从已有的单据上复制数据,克隆成一条新的记录.本文将介绍如何克隆一条记录,包括它的子单据以生成一条新的 ...
随机推荐
- (3)选择元素——(3)$()方法(The $() function)
No matter which type of selector we want to use in jQuery, we always start with the same function: $ ...
- android 项目中使用到的网络请求框架以及怎样配置好接口URL
我们在做项目中一定少不了网络请求,如今非常多公司的网络请求这块好多都是使用一些比較好的开源框架,我项目中使用的是volley,如今讲讲一些volley主要的使用,假设想要具体的了解就要去看它的源代码了 ...
- Android短彩信源码解析-短信发送流程(二)
转载请注明出处:http://blog.csdn.net/droyon/article/details/11699935 2,短彩信发送framework逻辑 短信在SmsSingleRecipien ...
- web.xml 中<taglib>报错(转载)
在web.xml加入taglib <taglib> <taglib-uri>/WEB-INF/tiles.tld</taglib- uri> <taglib- ...
- .Net Core 环境搭建
.Net Core 系列:1.环境搭建 前言: 2016年6月28日微软宣布发布 .NET Core 1.0.ASP.NET Core 1.0 和 Entity Framework Core 1.0. ...
- HTTPS 中双向认证SSL 协议的具体过程
HTTPS 中双向认证SSL 协议的具体过程: 这里总结为详细的步骤: ① 浏览器发送一个连接请求给安全服务器.② 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器.③ 客户浏览器检查服务器送 ...
- 集合判断null
Java 引用和指针差不多,null 引用 相当于 C++的空指针. isEmpty() 用于判断List内容是否为空,即表里一个元素也没有, 但是必须在 List<MallNews> g ...
- php启用gzip压缩
GZIP(GNU-ZIP)是一种压缩技术.经过GZIP压缩后页面大小可以变为原来的30%甚至更小.这样用户浏览的时候就会感觉很爽很愉快! 要实现GZIP压缩页面需要浏览器和服务器共同支持,实际上就是服 ...
- 一步一步重写 CodeIgniter 框架 (10) —— 使用 CodeIgniter 类库(续)
上一节简单实现了 CI 的类库扩展模型,所以 _ci_load_class 和 _ci_init_class 写的不是很完备.根据上节课的分析,当 system/libraries 目录下存在 Ema ...
- ReviewBoard安装和配置札记
眼下部门还没有採用Pair Programming那种时时刻刻都在review代码的工作方式,代码Review多採用走查方式,即代码写完后召开一个Code Review的Meeting,集中时间和经验 ...