zoukankan      html  css  js  c++  java
  • C++面试题(三)

    1 什么是函数对象?有什么作用?

    函数对象却具有许多函数指针不具有的有点,函数对象使程序设计更加灵活,而且能够实现函数的内联(inline)调用,使整个程序实现性能加速。

    函数对象:这里已经说明了这是一个对象,而且实际上只是这个对象具有的函数的某些功能,我们才称之为函数对象,意义很贴切,如果一个对象具有了某个函数的功能,我们变可以称之为函数对象。
    如何使对象具有函数功能呢,很简单,只需要为这个对象的操作符()进行重载就可以了,如下:

    class A{
    public:
    int operator()(int x){return x;}
    };
    A a;
    a(5);

    这样a就成为一个函数对象,当我们执行a(5)时,实际上就是利用了重载符号()。
    函数对象既然是一个“类对象”,那么我们当然可以在函数形参列表中调用它,它完全可以取代函数指针!

    如果说指针是C的标志,类是C++特有的,那么我们也可以说指针函数和函数对象之间的关系也是同前者一样的!(虽然有些严密)。当我们想在形参列表中调用某个函数时,可以先声明一个具有这种函数功能的函数对象,然后在形参中使用这个对象,他所作的功能和函数指针所作的功能是相同的,而且更加安全。
    下面是一个例子:

    class Func{
    public:
        int operator() (int a, int b)
        {
            cout<<a<<'+'<<b<<'='<<a+b<<endl;
            return a;
        }
    };
    int addFunc(int a, int b, Func& func)
    {
        func(a,b);
        return a;
    }
    Func func;
    addFunc(1,3,func);

    上述例子中首先定义了一个函数对象类,并重载了()操作符,目的是使前两个参数相加并输出,然后在addFunc中的形参列表中使用这个类对象,从而实现两数相加的功能。
    如果运用泛型思维来考虑,可以定一个函数模板类,来实现一般类型的数据的相加:

    class
    FuncT{ public: template<typename T> T operator() (T t1, T t2) { cout<<t1<<'+'<<t2<<'='<<t1+t2<<endl; return t1; } }; template <typename T> T addFuncT(T t1, T t2, FuncT& funct) { funct(t1,t2); return t1; } FuncT funct; addFuncT(2,4,funct); addFuncT(1.4,2.3,funct);

    2 STL迭代器种类?

    五类迭代器如下:

          (1)输入迭代器:只读,一次传递
           为输入迭代器预定义实现只有istream_iterator和istreambuf_iterator,用于从一个输入流istream中读取。一个输入迭代器仅能对它所选择的每个元素进行一次解析,它们只能向前移动。一个专门的构造函数定义了超越末尾的值。总是,输入迭代器可以对读操作的结果进行解析(对每个值仅解析一次),然后向前移动。
         (2)输出迭代器:只写,一次传递
           这是对输入迭代器的补充,不过是写操作而不是读操作。为输出迭代器的预定义实现只有ostream_iterator和ostreambuf_iterator,用于向一个输出流ostream写数据,还有一个一般较少使用的raw_storage_iterator。他们只能对每个写出的值进行一次解析,并且只能向前移动。对于输出迭代器来说,没有使用超越末尾的值来结束的概念。总之,输出迭代器可以对写操作的值进行解析(对每一个值仅解析一次),然后向前移动。
          (3)前向迭代器:多次读/写
           前向迭代器包含了输入和输出迭代器两者的功能,加上还可以多次解析一个迭代器指定的位置,因此可以对一个值进行多次读/写。顾名思义,前向迭代器只能向前移动。没有为前向迭代器预定义迭代器。
          (4)双向迭代器:operator--
           双向迭代器具有前向迭代器的全部功能。另外它还可以利用自减操作符operator--向后一次移动一个位置。由list容器中返回的迭代器都是双向的。
          (5)随机访问迭代器:类似于一个指针
           随机访问迭代器具有双向迭代器的所有功能,再加上一个指针所有的功能(一个指针就是一个随机访问迭代器),除了没有一种“空(null)”迭代器和空指针对应。基本上可以这样说,一个随机访问迭代器就像一个指针那样可以进行任何操作,包括使用操作符operator[]进行索引,加某个数值到一个指针就可以向前或者向后移动若干个位置,或者使用比较运算符在迭代器之间进行比较。

    迭代器类别

    说明

    输入迭代器

    从容器中读取元素。输入迭代器只能一次读入一个元素向前移动,输入迭代器只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列

    输出迭代器

    向容器中写入元素。输出迭代器只能一次一个元素向前移动。输出迭代器只支持一遍算法,统一输出迭代器不能两次遍历一个序列

    正向迭代器

    组合输入迭代器和输出迭代器的功能,并保留在容器中的位置

    双向迭代器

    组合正向迭代器和逆向迭代器的功能,支持多遍算法

    随机访问迭代器

    组合双向迭代器的功能与直接访问容器中任何元素的功能,即可向前向后跳过任意个元素

    迭代器的操作:

    每种迭代器均可进行包括表中前一种迭代器可进行的操作。迭代器的操作本质上是通过重载运算符来实现的,迭代器支持何种操作和能够执行什么运算是由迭代器所重载的运算符来决定的。

    迭代器类型 操作类型 说明

    所有迭代器

    p++

    ++p

    后置自增迭代器

    前置自增迭代器s's

    输入迭代器

    *p

    p=p1

    p==p1

    p!=p1

    复引用迭代器,作为右值

    将一个迭代器赋给另一个迭代器

    比较迭代器的相等性

    比较迭代器的不等性

    输出迭代器

    *p

    p=p1

    复引用迭代器,作为左值

    将一个迭代器赋给另一个迭代器

    正向迭代器

    提供输入输出迭代器的所有功能

    双向迭代器

    --p

    p--

    前置自减迭代器

    后置自减迭代器

    随机访问迭代器

    p+=i

    p-=i

    p+i

    p-i

    p[i]

    p<p1

    p<=p1

    p>p1

    p>=p1

    将迭代器递增i位

    将迭代器递减i位

    在p位加i位后的迭代器

    在p位减i位后的迭代器

    返回p位元素偏离i位的元素引用

    如果迭代器p的位置在p1前,返回true,否则返回false

    p的位置在p1的前面或同一位置时返回true,否则返回false

    3 10进制和16进制转换

    4 不用if,while,do-while,for,打印出所有大于0小于k的整数.函数原型void printLess(int k)

    方法一:

    #include <iostream> 
    using namespace std; 
    void printLess(int k){ 
       switch(--k){ 
       case 0:return; 
       default: 
       cout << k << endl; 
       printLess(k); 
       } 
    } 
    int main(){ 
       printLess(10);    
    } 

    方法二:

    void printLess(int k)
    {
      k > 0 ? (k < 10? printf("%d", k): NULL) : NULL;
      k > 0 ? printLess(k - 1) : NULL;

    }

    int main()
    {
    printLess(10);

    }

    5  char(127>>1 +1)

    6  volatile/explicit的用法

           volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存.

      首先, C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).

      那么显示声明的构造函数和隐式声明的有什么区别呢? 我们来看下面的例子:

    class CxString  // 没有使用explicit关键字的类声明, 即默认为隐式声明  
    {  
    public:  
        char *_pstr;  
        int _size;  
        CxString(int size)  
        {  
            _size = size;                // string的预设大小  
            _pstr = malloc(size + 1);    // 分配string的内存  
            memset(_pstr, 0, size + 1);  
        }  
        CxString(const char *p)  
        {  
            int size = strlen(p);  
            _pstr = malloc(size + 1);    // 分配string的内存  
            strcpy(_pstr, p);            // 复制字符串  
            _size = strlen(_pstr);  
        }  
        // 析构函数这里不讨论, 省略...  
    };  
        // 下面是调用:  
        CxString string1(24);     // 这样是OK的, 为CxString预分配24字节的大小的内存  
        CxString string2 = 10;    // 这样是OK的, 为CxString预分配10字节的大小的内存  
        CxString string3;         // 这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用  
        CxString string4("aaaa"); // 这样是OK的  
        CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
        CxString string6 = 'c';   // 这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码  
        string1 = 2;              // 这样也是OK的, 为CxString预分配2字节的大小的内存  
        string2 = 3;              // 这样也是OK的, 为CxString预分配3字节的大小的内存  
        string3 = string1;        // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用free释放_pstr内存指针的时候可能会报错, 完整的代码必须重载运算符"=", 并在其中处理内存释放 

    上面的代码中, "CxString string2 = 10;" 这句为什么是可以的呢? 在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 "CxString string2 = 10;" 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作:

    CxString string2(10);  
    或  
    CxString temp(10);  
    CxString string2 = temp;  

    但是, 上面的代码中的_size代表的是字符串内存分配的大小, 那么调用的第二句 "CxString string2 = 10;" 和第六句 "CxString string6 = 'c';" 就显得不伦不类, 而且容易让人疑惑. 有什么办法阻止这种用法呢? 答案就是使用explicit关键字. 我们把上面的代码修改一下, 如下:

    class CxString  // 使用关键字explicit的类声明, 显示转换  
    {  
    public:  
        char *_pstr;  
        int _size;  
        explicit CxString(int size)  
        {  
            _size = size;  
            // 代码同上, 省略...  
        }  
        CxString(const char *p)  
        {  
            // 代码同上, 省略...  
        }  
    };  
      
        // 下面是调用:  
      
        CxString string1(24);     // 这样是OK的  
        CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换  
        CxString string3;         // 这样是不行的, 因为没有默认构造函数  
        CxString string4("aaaa"); // 这样是OK的  
        CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
        CxString string6 = 'c';   // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换  
        string1 = 2;              // 这样也是不行的, 因为取消了隐式转换  
        string2 = 3;              // 这样也是不行的, 因为取消了隐式转换  
        string3 = string1;        // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载  

    explicit关键字的作用就是防止类构造函数的隐式自动转换.

    上面也已经说过了, explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了. 例如: 

    class CxString  // explicit关键字在类构造函数参数大于或等于两个时无效  
    {  
    public:  
        char *_pstr;  
        int _age;  
        int _size;  
        explicit CxString(int age, int size)  
        {  
            _age = age;  
            _size = size;  
            // 代码同上, 省略...  
        }  
        CxString(const char *p)  
        {  
            // 代码同上, 省略...  
        }  
    };  
      
        // 这个时候有没有explicit关键字都是一样的  

    但是, 也有一个例外, 就是当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数, 例子如下:

    class CxString  // 使用关键字explicit声明  
    {  
    public:  
        int _age;  
        int _size;  
        explicit CxString(int age, int size = 0)  
        {  
            _age = age;  
            _size = size;  
            // 代码同上, 省略...  
        }  
        CxString(const char *p)  
        {  
            // 代码同上, 省略...  
        }  
    };  
      
        // 下面是调用:  
      
        CxString string1(24);     // 这样是OK的  
        CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换  
        CxString string3;         // 这样是不行的, 因为没有默认构造函数  
        string1 = 2;              // 这样也是不行的, 因为取消了隐式转换  
        string2 = 3;              // 这样也是不行的, 因为取消了隐式转换  
        string3 = string1;        // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载 

    7  字节对齐问题

    struct A {
    char a; // 1
    float d; // 4
    char b; // 1
    short c; // 2
    double e; // 8
    };

    struct B {
    char a; // 1
    char b; // 1
    short c; // 2
    float d; // 4
    double e; // 8
    };

    笔试的时候遇到上面的题,打错了,其实,理解下面三条就能做出来了:

    1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
    2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
    3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

      sizeof(A) = 24

      sizeof(B) =16

    8 数据库事物

    9 保证应用程序只有一个实例

    方法一:用Mutex互斥量来实现,内核对象,可以用来进程间互斥,但是此时不能取得已经启动的实例窗口局柄,因此无法激活已经启动的实例窗口

    #include "stdafx.h"
    #include <iostream>
    #include <windows.h>
    #include <wtypes.h>
     
    int main()
    {
     
        BOOL bExist = FALSE;
        HANDLE hMutex = ::CreateMutex(NULL,TRUE,L"一个唯一的名字");
     
        if (GetLastError() == ERROR_ALREADY_EXISTS)
        {
            bExist = TRUE;
        }
        if (hMutex)
        {
            ::ReleaseMutex(hMutex);
        }
        if(bExist)
        {
            return 0;
        }
        while(1)
        {
            std::cout<<"hello world";
        }
        getchar();
        return 0;
    }

    方法二
    一般来说,使程序只运行一个实例的最简单的方法当然是使用FindWindow()查找主窗口,如果主窗口已经存在了,当然说明已经有一个实例运行了。代码如下:
    // 这种方法有缺陷,窗口名字改变之后就再也找不到了,FindWindow()的参数ClassName和Caption比较难取得。
    HWND hWnd = FindWindow(NULL, TEXT("SingleInstanceFW"));
    if(IsWindow(hWnd))
    {
     ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);
     ::ShowWindow(hWnd, SW_NORMAL);     // 显示
     ::SetForegroundWindow(hWnd);       // 激活
     return FALSE;
    }

    方法三

    这种方法相比上面两种方法,避免上面两种方法的缺点,通过SetProp()为程序主窗口设置一个特殊的Property,然后在启动时遍历所有的窗口,找出包含着个Property的窗口局柄

    。【这个附加的窗口属性在窗口销毁时也应该销毁】这个方法的缺点就是代码比较多一点,如下:

    // 声明全局的 属性 名和 属性值
    TCHAR g_strKSCoreAppName[] = _T("AFX_KSInstall_CPP__12036F8B_8301_46e2_ADC5_A14A44A85877__");
    HANDLE g_hValue = (HANDLE)1022;

    // 定义枚举窗口回调函数
    BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lParam)
    {
     //TCHAR str[200] = {0};
     //::GetWindowText(hwnd, str, 200);
     HANDLE h = GetProp(hwnd, g_strKSCoreAppName);
     if(h == g_hValue)
     {
      *(HWND*)lParam = hwnd;
      return FALSE;
     }
     return TRUE;
    }

    // 主窗口创建前判断
    HWND oldHWnd = NULL;
    ::EnumWindows(EnumWndProc,(LPARAM)&oldHWnd);    //枚举所有运行的窗口
    if (oldHWnd != NULL)
    {
     ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);

     ::ShowWindow(oldHWnd, SW_NORMAL);     // 显示
     ::SetForegroundWindow(oldHWnd);       // 激活
     return FALSE;
    }

    // 主窗口创建后设置,为窗口附加一个属性
    ::SetProp(m_hWnd, g_strKSCoreAppName, g_hValue);

    // 主窗口退出时移除该附加属性
    ::RemoveProp(m_hWnd, g_strKSCoreAppName);

    方法四

    上面的方法二和方法三都有一个弊病,不知道大家发现没,那就是依赖于窗口的存在,没有窗口的程序怎么办了,用方法一是可以的,不过方法一不太适合即时修改状态,譬如我想提供选项给用户,可以即时修改是否允许多实例,像KMP就提供了即时修改是否允许多实例,使用全局变量是一个比较好的解决方案,使用全局共享变量的方法则主要是在VC框架程序中通过编译器来实现的。通过#pragma data_seg预编译指令创建一个新节,在此节中可用volatile关键字定义一个变量,而且必须对其进行初始化。Volatile关键字指定了变量可以为外部进程访问。最后,为了使该变量能够在进程互斥过程中发挥作用,还要将其设置为共享变量,同时允许具有读、写访问权限。这可以通过#pragma comment预编译指令来通知编译器。下面给出使用了全局变量的进程互斥代码清单:

    #pragma data_seg("Shared") 
    int volatile g_lAppInstance = 0; 
    #pragma data_seg() 
    #pragma comment(linker,"/section:Shared,RWS")

    if (0 == g_lAppInstance)
    {
     g_lAppInstance = 1;
    }
    else if (1 == g_lAppInstance)
    {
     ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);
     return FALSE;
    }
    else
    {
     // 直接启动
    }

    10 如何main()函数之前执行函数

  • 相关阅读:
    SpringBoot 线程池配置 定时任务,异步任务
    ArcMap 5 同一地区,多图层不能一起显示
    ArcMap 4 完全卸载ArcGIS
    ArcMap 3 矢量数据下载(下载、应用需谨慎)
    ArcMap 2 数据矢量化
    MODIS系列之NDVI(MOD13Q1)九:单一土地类型NDVI及时序图(以耕地类型为例)
    ArcMap 1 矢量数据和栅格数据
    ArcMap 0 (ArcGIS10.2安装(完善版--能解决常见问题))
    MODIS系列之NDVI(MOD13Q1)八:土地利用数据(又称地表覆盖数据)下载
    MODIS系列之NDVI(MOD13Q1)七:时间序列S-G滤波之Python
  • 原文地址:https://www.cnblogs.com/liufei1983/p/7181335.html
Copyright © 2011-2022 走看看