【数据结构与算法(二十五)】

题目

求1+2+……+n

求1+2+……+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(?:)

思路

循环和递归都不能用了,因为if不能用

解法1:利用构造函数求解

这种解法依旧是围绕循环做的。循环只是让相同的代码重复执行n遍而已,完全不用for和while来达到这个结果。比如定义一个类型,接着创建n个该类型的实例,那么这个类型的构造函数将确定会被调用n次

class Temp {
public:
    Temp() { ++N; Sum += N; }
    //C++静态成员函数
    static void Reset() { N = 0; Sum = 0; }
    static unsigned int GetSum() { return Sum; }

private:
    //C++静态数据成员
    static unsigned int N;
    static unsigned int Sum;
};
//定义并初始化静态数据成员
unsigned int Temp::N = 0;
unsigned int Temp::Sum = 0;

unsigned int Sum_1(unsigned int n)
{
    Temp::Reset();
    //创建n个该类型的实例
    Temp *a = new Temp[n];
    //unsigned int result=a->GetSum();
    delete[] a;
    a = nullptr;
//调用类的静态成员函数
    return Temp::GetSum();
}
知识点——C++ 静态成员函数和静态数据成员

静态数据成员
1、对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所公有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象公用。所以,静态数据成员的值对每个对象都是一样的,它的值可以被更新
2、静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类中定义。unsigned int Temp::N = 0;是定义静态数据成员
3、因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它
4、静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为数据类型 类名::静态数据成员名=值;
5、类的静态数据成员有两种访问形式:类对象名.静态数据成员名或者类型名::静态数据成员名

静态成员函数
1、静态成员函数为类的全部服务而不是为类的某一个具体对象服务。与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象,因为普通成员函数总是具体属于某个类的具体对象的。与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其他的静态成员函数
参考:https://www.cnblogs.com/BeyondAnyTime/archive/2012/06/08/2542315.html

解法2:利用虚函数求解

围绕递归进行的算法。因为不能在一个函数中判断是不是应该终止递归,所以考虑定义两个函数,一个函数充当递归函数的角色,另一个函数处理终止递归的情况。我们需要做的就是在两个函数里二选一,从二选一于是就想到了布尔变量,比如值为true(1)的时候调用第一个函数,值为false(0)的时候调用第二个函数。如何把数值变量n转换成布尔值。如果对n连续做两次反运算,即!!n,那么非零的n转换成true,0转换为false

class A;
//指针声明不调用构造函数
//全局变量
A* Array[2];

class A
{
public:
    virtual unsigned int Sum(unsigned int n) { return 0; }
};
//作为循环的类
class B :public A
{
public:
    virtual unsigned int Sum(unsigned int n) {
    //当n不为0时,Array[!!n]是类型B==>Array[1]=&b,所以就递归调用class B的Sum函数
        return Array[!!n]->Sum(n - 1) + n;
    }
};
int Sum_2(int n)
{
    A a;
    B b;
    Array[0] = &a;
    Array[1] = &b;

    int value = Array[1]->Sum(n);
    return value;
}
知识点——C++ virtual虚函数

1、虚函数是面向对象编程中函数的一种特定形态,是C++中用于实现多态的一种有效机制
2、什么是虚函数?
指向基类的指针在操作它的多态类对象时,会根据不同的类对象调用其响应的类的成员函数。虚函数用virtual修饰函数名。虚函数的作用时在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数进行重新定义。在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型,以实现统一的接口。如果派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
以下是使用虚函数需要注意的地方
3、只需要在声明函数的类体中加上关键字virtual将函数声明为虚函数,实现函数时不需要使用关键字virtual
4、当将基类中的某一成员函数声明为虚函数后,派生类的同名函数自动称为虚函数
5、非类的成员函数不可能为虚函数,全局函数以及类的静态成员函数和构造也不能定义为虚函数,但可以将析构函数定义为虚函数。将基类的析构函数定义为虚函数后,当delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
6、普通派生类对象,先调用基类构造再调用派生类构造
7、基类的析构函数应该定义为虚函数,这样可以在实现多态的时候不造成内存泄露。基类析构函数未声明virtual,基类指针指向派生类时,delete指针不调用派生类析构函数。有virtual,则先调用派生类析构再调用基类析构。
8、基类指针动态建立派生类对象,普通调用派生类构造函数
9、指针声明不调用构造函数
10、虚函数的使用可以极大地提高软件开发的效率,虚函数是通过什么实现的?
11、虚函数是通过一张虚函数表来实现的。该表是一个类的虚函数的地址表,解决了继承、覆盖的问题,保证它能真实反应实际的函数。这样,在有虚函数的类的实例中,此表被分配在实例的内存中,所以当用父类的指针来操作一个子类的时候,这张虚函数表就指明了实际所应该调用的函数。
12、C++的编译器能够保证虚函数表的指针存在于对象实例中最前面的位置,通过对象实例的地址得到这张虚函数表,然后就可以遍历其中的函数指针,并调用相应的函数。
13、根据构造函数的调用顺序,要先调用父类的构造函数,此时编译器只“看到”父类,并不知道后面是否还有继承者,它初始化父类对象的虚函数表的指针,该虚函数表指向父类的虚函数表。当执行子类的构造函数时,子类对象的虚函数表指针被初始化,指向自身的虚函数表
14、编译器发现一个类中有虚函数,便会立即为此类生成虚函数表,虚函数表的各表项为指向对应虚函数的指针。。编译器还会在此类中隐含插入一个指针vptr(对VC编译器来说,它插在类的第一个位置上)指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含vptr与vtable的关联代码,将vptr指向对应的vtable,将类与此类的vtable联系起来,另外在调用类的构造函数时,指向基类的指针此时已经变成指向具体的类的this指针,这样此this指针即可得到正确的vtable。这样才能真正与函数体连接,这就是动态联编,实现多态的基本原理

解法3:利用函数指针求解

在纯C语言的编译环境中,不能使用虚函数,此时可以用函数指针来模拟递归的过程

typedef unsigned int(*fun)(unsigned int);

unsigned int teminator(unsigned int n)
{
    return 0;
}

unsigned int Sum_3(unsigned int n)
{
//f[0]=unsigned int teminator(unsigned int n);
//f[1]=unsigned int Sum_3(unsigned int n);
    static fun f[2] = { teminator,Sum_3 };
    return n + f[!!n](n - 1);
}
解法4:利用模板类型求解
template <unsigned int n>struct Sum_4
{
    enum Value
    {
        N = Sum_4<n - 1>::N + n;
    };
};

template <> struct Sum_4<1>
{
    enum Value{N=1};
};
分析:

Sum_4<100>::N就是1+2+……+100的结果。当编译器看到Sum_4<100>时,就会为模板类Sum_4以参数100生成该类型的代码,但以100为参数的类型需要得到以99为参数的类型,因为Sum_4<100>::N=Sum_4<99>::N+100。这个过程会一直递归到参数为1的类型,由于该类型已经显式定义,编译器无须生成,递归编译到此结束。由于这个过程是在编译过程中完成的,因此要求输入n必须是编译期间就能确定的常量,不能动态输入。而且编译器对递归编译代码的递归深度是有限制的,也就是要求n不能太大。
C++中模板template的使用https://www.cnblogs.com/cynchanpin/p/7127897.html

考题:Template有什么特点?什么时候用?

1、Template可以独立于任何特定的类型编写代码,是泛型编程的基础
2、当我们编写的类和函数能够多态地跨越编译时的不相关的类型时使用
3、模板类能实现抽象和效率的结合,同时template还能有效防止代码膨胀
4、C++为什么使用模板类
①可以用来创建动态增长和减小的数据结构
②类型无关,因此具有很高的可复用性
③编译时而不是运行时检查数据类型,保证了类型安全
④平台无关,具有可以知兴替
⑤可用于基本数据类型

考题:函数模板与类模板有什么区别?

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地制定

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值