C++--第14课 - 专题二经典问题解析

第14课 - 专题二经典问题解析

1. malloc与free和new与delete有什么区别?

malloc和free是函数,new和delete是关键字。

#include <cstdlib>

#include <iostream>

using namespace std;

class Test

{

private:

int i;

public:

Test()

{

cout<<"Test()"<<endl;

i = 0;

}

Test(int i)

{

cout<<"Test(int i)"<<endl;

this->i = i;

}

~Test()

{

cout<<"~Test"<<endl;

}

int getI()

{

return i;

}

};

void func()

{

int* p = reinterpret_cast<int*>(malloc(sizeof(int))); //在堆上申请空间

//reinterpret_cast是C++中强制类型转换的关键字

int* q = new int(10);  //在堆上申请空间

*p = 5;

//*q = 10;

cout<<*p<<" "<<*q<<endl;

free(p);

delete q;

//仅从上面的两段程序来看,new关键字只是比malloc关键字多了可以在初始化时赋值

Test* op = reinterpret_cast<Test*>(malloc(sizeof(Test)));

Test* oq = new Test;

cout<<op->getI()<<" "<<oq->getI()<<endl;

free(op);

delete oq;

}

int main(int argc, char *argv[])

{

func();

cout << "Press the enter key to continue ...";

cin.get();

return EXIT_SUCCESS;

}

运行结果:

5 10

Test<>

6230240  0

~Test

op指向一段内存空间,但是没有正常调用构造函数。因为malloc不具备初始化的功能,所以malloc函数肯定也不能调用构造函数。但是new是可以调用构造函数的。

注意:

(1)      malloc和free是库函数,以字节为单位申请堆内存。

(2)      new和delete是关键字,以类型为单位申请堆内存。

(3)      malloc和free单纯的对内存进行申请与释放。

(4)      对于基本类型new关键字会对内存进行初始化。

(5)      对于类类型new和delete还负责构造函数和析构函数的调用。

2. 编译器对构造函数的调用

#include <cstdlib>

#include <iostream>

using namespace std;

class Test

{

public:

Test(int i)

{

cout<<"Test(int i)"<<endl;

}

Test(const Test& obj) //拷贝构造函数

{

cout<<"Test(const Test& obj)"<<endl;

}

~Test()

{

cout<<"~Test"<<endl;

}

};

void func()

{

Test t1(5);  //最标准的初始化方式

Test t2 = 5; //手工赋值

Test t3 = Test(5);

}

int main(int argc, char *argv[])

{

func();

cout << "Press the enter key to continue ...";

cin.get();

return EXIT_SUCCESS;

}

运行结果:

Test<int i>

Test<int i>

Test<int i>

~Test

~Test

~Test

在现在的C++编译器看来,以上的三种初始化方式是一样的。

l  C++编译器会尝试各种手段尝试让程序通过编译

(1)      方式一:尽力匹配重载函数。

(2)      方式二:尽力使用函数的默认参数。

(3)      方式三:尽力尝试调用构造函数进行类型转换。

l  对于我们上面的例子Test t2 = 5;

(1)      默认情况下,字面量5的类型为int,因此5无法直接用于初始化Test对象;

(2)      但是编译器在默认情况下可以自动调用构造函数;

(3)      于是编译器尝试调用Test(int)生成一个临时对象;

(4)      之后调用拷贝构造函数Test(const Test&)用临时对象对t2进行初始化。

l  对于以前的编译器

方案A:Test t1 = 5;  ??  Test t1 = Test(Test(5));

这是最开始C++使用的编译方法,显然这种方法是效率底下的,所以我们使用了下面的这种方法。

方案B:Test t1 = 5;  ??  Test t1(5);

经过了这种方式而定优化,我们看到了我们使用上面三种方式的初始化方法都是一样的,并没有调用拷贝函数这个步骤。

3. explicit关键字

“剥夺”编译器对构造函数的调用尝试。C++提供了explicit关键字用于阻止编译器对构造函数的调用尝试。

例如:

class Test

{

public:

explicit Test(int i)

{

cout<<"Test(int i)"<<endl;

}

explicit Test(const Test& obj) //拷贝构造函数

{

cout<<"Test(const Test& obj)"<<endl;

}

~Test()

{

cout<<"~Test"<<endl;

}

};

当我们在上面的例子里面使用上面的程序,我们会发现运行的时候会出错。因为我们剥夺了编译器主动调用构造函数的权利。

4. 静态成员能用来做什么

对象数目控制,一个类最多只能有一个对象存在于系统中,如何实现?

现实生活中就是一个汽车只能配备自己的发动机一样。

程序—单例模式的实现

#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;

cInstance = new Singleton(); /*是这个类的成员函数就能访问该类的

成员变量 */

}

return cInstance;

}

void print()

{

cout<<"I‘m Singleton!"<<endl;

}

};

Singleton* Singleton::cInstance = NULL; //在外边给静态成员函数申请空间

void func()

{

Singleton* s = Singleton::GetInstance(); //s这个指针指向成员函数中的返回值

Singleton* s1 = Singleton::GetInstance();

Singleton* s2 = Singleton::GetInstance();

/*调用了三次 Singleton::GetInstance()函数,但是 new Singleton()只打印了一次

而且三次的地址都是一样的,这就是工程里面常用的单列函数*/

cout<<s<<" "<<s1<<" "<<s2<<endl;

s->print();

}

int main(int argc, char *argv[])

{

func();

cout << "Press the enter key to continue ...";

cin.get();

return EXIT_SUCCESS;

}

运行结果:

new Singleton()

0x5e10c0 0x5e10c0 0x5e10c0

I’m Singleton!

5. 两种特殊的函数

这两种函数在正常的C语言和C++中是不使用的,但是我们还是要介绍一下。

无状态函数:函数的调用结果只与实参值相关。

状态函数:函数的调用结果不仅与实参值相关还与之前的函数调用有关。

#include <cstdlib>

#include <iostream>

using namespace std;

int fib1(int i)

/*斐波那契数列函数,它的结果只是与实参的值有关,是无状态函数*/

{

int a1 = 0;

int a2 = 1;

int ret = a2;

while( i > 1)

{

ret = a2 + a1;

a1 = a2;

a2 = ret;

i--;

}

return ret;

}

int fib2()

/*状态函数,在函数的内部使用了两个静态局部变量,每次调用都会改变静态局部变量的

值。它的功能是第n次调用就返回斐波那契数列的第n项*/

{

static int a1 = 0;

static int a2 = 1;

int ret = a2;

int t = a2;

a2 = a2 + a1;

a1 = t;

return ret;

}

int main(int argc, char *argv[])

{

for(int i=1; i<=10; i++)

{

cout<<fib1(i)<<endl;

}

for(int i=1; i<=10; i++)

{

cout<<fib2()<<endl;

}

cout << "Press the enter key to continue ...";

cin.get();

return EXIT_SUCCESS;

}

分析:

l  fib1是以无状态函数的方式实现的,求解数列每一项时都会做重复的循环,时间复杂度为O(n)。

l  fib2是以状态函数的方式实现的,每调用一次就可以得到数列当前项的值,时间复杂度为O(1),但是无法从头再来。

我们现在来想,是否有一个折中的方法,既可以提高效率又可以降低复杂度。C语言中这种方式是实现不了的,但是在C++中这是可以实现的。我们具体来看一下下面的程序。

l  程序—函数对象的实现,通过重载来实现。

#include <cstdlib>

#include <iostream>

using namespace std;

int fib1(int i)

{

int a1 = 0;

int a2 = 1;

int ret = a2;

while( i > 1)

{

ret = a2 + a1;

a1 = a2;

a2 = ret;

i--;

}

return ret;

}

int fib2()

{

static int a1 = 0;

static int a2 = 1;

int ret = a2;

int t = a2;

a2 = a2 + a1;

a1 = t;

return ret;

}

class Fib

{

private:

int a1;

int a2;

public:

Fib() //给a1和a2赋初值。

{

a1 = 0;

a2 = 1;

}

int operator() () //重载函数操作 符

{

int ret = a2;

int t = a2;

a2 = a2 + a1;

a1 = t;

return ret;

}

};

int main(int argc, char *argv[])

{

cout<<"int fib1(int i)"<<endl;

for(int i=1; i<=10; i++)

{

cout<<fib1(i)<<endl;

}

cout<<endl;

cout<<"int fib2()"<<endl;

for(int i=1; i<=10; i++)

{

cout<<fib2()<<endl;

}

cout<<endl;

//下面是新加入的段落

Fib fib;

cout<<"Fib fib;"<<endl;

for(int i=1; i<=10; i++)

{

cout<<fib()<<endl;/*fib是一个对象,我们能像一个函数一样来使用吗?

我们正常来写就是fib().operator(),简写就是我们程序中的样子*/

}

cout<<endl;

cout << "Press the enter key to continue ...";

cin.get();

return EXIT_SUCCESS;

}

原文地址:https://www.cnblogs.com/free-1122/p/11336213.html

时间: 2024-11-10 08:14:33

C++--第14课 - 专题二经典问题解析的相关文章

C++--第24课 - 专题四经典问题解析

第24课 - 专题四经典问题解析 1. 历史的痕迹 #include <cstdlib> #include <iostream> using namespace std; template<class T>  //以前是用typename定义,现在是用class定义 T Minus(T a, T b) { return a - b; } template<class T>  //类模板 class Add { public: T add(T a, T b)

专题一经典问题解析-7

一.const和引用的疑惑 #include <stdio.h> int main() { const int x = 1; const int& rx = x; int& nrx = const_cast<int&>(rx); nrx = 5; printf("x = %d\n", x); printf("rx = %d\n", rx); printf("nrx = %d\n", nrx); pr

分析Linux磁盘管理与文件系统专题二

1.关于设备文件 A 我们知道常见的设备文件有:字符设备文件(character),块设备文件(block). B 块设备,简写b,随机访问,比如硬盘. C 字符设备,简写c,线性访问,比如键盘,鼠标,显示器. D 我们的设备文件常常在/dev目录下,并且没有大小.因为设备文件只是作为设备访问   的入口. E 设备文件,一般用major(主设备号),minor(次设备号)进行标示.主设备号标示设备类型,次设备号标示同一种类型设备下的不同设备. 2.创建设备文件实例 [[email protec

UI标签库专题二:JEECG智能开发平台Column(列) 子标签

 1.1. Column(列) 子标签 1.1.1. 示例 <t:dgCol title="年龄" field="age" query="true" extend="{data-options:{required:false,groupSeparator:\"','\"},class:{value:'easyui-numberbox'}}"></t:dgCol> 1.1.2. 参

Spark3000门徒第14课spark RDD解密总结

今晚听了王家林老师的第14课spark RDD解密,课堂笔记如下: Spark是基于工作集的应用抽象,RDD:Resillient Distributed Dataset是基于工作集的,spark可以对结果重用. 位置感知:spark比hadoop更精致. RDD是lazy的,是分布式函数式编程的抽象,RDD可以看做一个只读的List或者Array.产生的中间结果怎么办? 不能让 他立即计算,采用Lazy级别,只对数据处理做标记.所以RDD操作是有向的,链式的,所以Stage有1000个步骤,不

NeHe OpenGL教程 第十八课:二次几何体

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第十八课:二次几何体 二次几何体: 利用二次几何体,你可以很容易的创建球,圆盘,圆柱和圆锥. 二次曲面是一种画复合对象的方法,这种方法通常并不需要很多的三角形.我们将要使用第七课的代码.我们将要增加7个变量以及修改纹理以增加一些变化

Tomcat专题二: JDK安装以及tomcat基本环境安装和使用

tomcat专题二:jdk安装以及tomcat基本安装和使用 书接tomcat专题一,在这一节将介绍一下java运行环境的安装以及tomcat的基本安装和使用.可能有人会问安装tomcat跟java运行环境有什么关系?正像专题一介绍的那样,tomcat本身只是一个业务框架,一个WEB容器,其底层还是基于jvm虚拟机来运行java程序的字节码文件.可以认为tomcat负责接受上层的应用请求,然后将请求的java程序交与jvm虚拟机执行并返回结果,这之间形成了一个调用关系,这在下面的tomcat启动

开发指南专题二:JEECG微云高速开发平台JEECG框架初探

开发指南专题二:JEECG微云高速开发平台JEECG框架初探 2.JEECG框架初探 2.1演示系统 打开浏览器输入JEECG演示环境界址:http://demo.jeecg.org:8090/能够看到如图21所看到的的登录界面., 图21演示系统登录界面 点击[登陆]button,进入演示系统的主界面,如图22所看到的. 图22演示系统主界面 在JEECG演示系统中的功能模块包含系统管理.流程管理.业务申请.业务办理.经常使用功能演示等.当中,用户管理.流程设计器的界面截图如图23和图24所看

FPGA入门学习第一课:二分频器

分频器还是比较简单的,一般的思路是:每数几个时钟就输出一个时钟.最简单的当数二分频器了,每当时钟上升沿(或下降沿)就把输出翻转一下.这样就刚好实现了二分频器了. 网上也搜到了最简实现”二分频最简单了,一句话就可以了:               always @ (negedge clk)        clk_2<=~clk_2;“ 但仿真时却发现无法输出 分析是因为输出信号的初始状态不确定造成的,于是加了一句初始化,就可以正常分频了 但观察他们生成的逻辑结构图是一样的 完整代码如下: mod