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学习笔记(十四)复制/克隆记录
经常有这样的需求,一个单据上有太多要填写的内容,有时还关联多个子单据,客户不想一个一个地填写,他们想从已有的单据上复制数据,克隆成一条新的记录.本文将介绍如何克隆一条记录,包括它的子单据以生成一条新的 ...
随机推荐
- Cocos2d-X学习——Android不同设备FPS不同问题
2014-07-16 环境:Cocos2dx 2.2.4 AppDelegate.cpp中FPS设置为 60 pDirector->setAnimationInterval(1.0 / 60); ...
- iOS中的图像处理(一)——基础滤镜
最近在稍微做一些整理,翻起这部分的代码,发现是两个多月前的了. 这里讨论的是基于RGBA模型下的图像处理,即将变换作用在每个像素上. 代码是以UIImage的category形式存在的: typede ...
- (void)(&x==&y)
#define max(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); ...
- 在Update Panel 控件里面添加 File Upload 控件 上传文件
Detail Information:http://www.codeproject.com/Articles/482800/FileplusUploadplusinplusUpdateplusPane ...
- C语言,单链表操作(增删改查)(version 0.1)
这天要面试,提前把链表操作重新写了一遍.备份一下,以备不时之需. 希望有人能看到这篇代码,并指正. // File Name : list.h #include "stdafx.h" ...
- poemel 端口作用
clientPort 用于connetor组件启动时候,监听的调用,用于客户端连接 port用于服务器间通信,即rpc调用时候使用,在remote组件启动时候,生成remote,即gateway实例, ...
- 配置系统引导启动SuperScoekt
配置系统引导启动SuperScoekt SuperSocket源码解析之启动过程 一 简介 这里主要说明从配置系统引导启动SuperScoekt作为应用程序,且以控制台程序方式启动 二 启动过程 2. ...
- 基于visual Studio2013解决算法导论之049活动选择问题
题目 活动选择问题 解决代码及点评 // 活动选择问题.cpp : 定义控制台应用程序的入口点. // #include<iostream> #define N 100 using ...
- Linux下VNC的安装和开机启动
1.确认VNC是否安装默认情况下,Red Hat Enterprise Linux安装程序会将VNC服务安装在系统上.确认是否已经安装VNC服务及查看安装的VNC版本[root@testdb ~]# ...
- iOS 本地化应用程序(NSLocalizedString)
App本地化的需要不用讲大家也都明白,本文将介绍一种简单的方法来实现字符串的本地化. 在不考虑本地化的情况下,我们如果在代码中给一个Button定义title,一般会这样写: btn.titleLab ...