zoukankan      html  css  js  c++  java
  • 【面试题001】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数

    【面试题001】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数 

    一,在C++中,有哪4个与类型转换相关的关键字?

        好多书籍,推荐使用类型转换的关键字,但是c风格的类型转换操作,确实很方便,但是不易掌握。

    1、const_cast

    号称唯一具有常量性移除的转型操作符,这个说法实在很废话,不解释。平时几乎没有用过,遇到需要这个关键字的时候,都是直接修改了接口的类型,也不会去用这个关键字,一般来说老接口设计有问题啊。明明是const的,非得转成non-const实在别扭。

    2、dynamic_cast

    号称安全向下转型(safe downcasting),就是把一个父类型转成它的子类型,如果不是父子关系则会返回0,比如一种用法:

    assert(dynamic_cast<derived*>(pBase));

    曾经认为是唯一好用又常用的转型操作符,但在吃过亏后发现也要三思而后用,比较喜欢无脑,所以不再喜欢它了。

    不止一本书上说这个操作符有性能问题,但是它们没有给出具体的度量值,也不会告诉你性能分析软件没法将它的耗时与语句直接对应上,比如会把使用这个 操作符的语句耗时显示在unknown分组中,太操蛋了。google的C++编码规范中也明确禁用此关键字,可惜我仍然还没反应过来,吃了大亏。

    总之,热点程序里面不要用。

    3、static_cast

    把编译器隐式执行的转型搞成显式的,特别是有告警的类型转换加上它就ok啦,比如double转int。偶尔用用,敲这么多字,还是C风格省心……

    4、reinterpret_cast

    对操作数的位模式做转化,比如把一个结构体转成char*。从来没用过,这名字实在陌生得紧,不看书真心想不起来。一般都会把源操作内存块转成void,然后使用的地方再找到想要的字段,转成想要的类型,工作中还没见过代码直接用的。

    二,

    定义一个空的类型,里面没有任何成员函数和成员变量。对这个类型求sizeof,得到的结果是1.

    因为当声明该类型的实例的时候,对象模型要求在内存当中占用一定的空间,否者无法使用这个实例,至于占用多大由编译器规定的对象模型决定。vs g++都占用1字节的空间,详细见深入理解C++对象模型。

    如果加入了构造函数和析构函数的话,这个类型的实例化变量的大小还是1,因为这两个函数的地址只与类型相关,而与类型的实例不相关,C++对象模型不会为这两个函数在实例化变量里面添加任何额外的信息。

    如果把析构函数改成虚拟函数的话,类里面有虚函数,就会为该类型生成虚函数表,并在该类型的每个实例化变量中添加一个指向虚函数表的指针,

    32位,求sizeof=4

    64位,求sizeof=8

     

    三,赋值运算符函数

    可以参考这个这个完整的实现了String

    String类的实现

    C++,拷贝构造函数的参数必须用引用,否者的话,如果你用了值传递,因为把形参复制到实参会调用拷贝构造函数,这样子就是在拷贝构造函数里面调用了拷贝构造函数,就会无休止的递归调用,从而导致栈溢出。

    因此C++不允许拷贝构造函数传值参数,而应该用A(const A & other);

    围绕构造函数,析构函数,运算符重载,

    四,赋值运算符函数

    根据CMyString类声明,请为该类型添加赋值运算符函数,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class CMyString
    {
    public:
        CMyString(char *pData = NULL);
        CMyString(const CMyString &str);
        ~CMyString(void);

    private:
        char *m_pData;
    };

    注意点:

    1.把返回值的类型声明为该类型的引用,并且返回自身的引用*this,这样子才可以做连续赋值。

    2.把传入的类型声明为常量的引用。如果是值传递的话会调用拷贝构造函数,这样子可以避免无所谓的消耗。同时我们在赋值运算符函数中不会改变传入的实例的状态,因此应该用const修饰。

    3.分配新的内存之前需要释放实例自身已有的内存。否者程序将出现内存泄漏。

    4.判断传入的参数和当前实例(*this)是不是同一个实例,如果是同一个不必进程赋值操作,如果没有判断,释放了自身的内存的时候会导致严重的问题。

    写代码的过程中发现了一个问题 NULL在那个头文件中的问题:

    NULL定义在stddef.h中,isstream与vector通过包含cstdef包含了stddef.h

    定义如下

    1
    2
    3
    4
    5
    6
     
    #undef NULL 
    #if defined(__cplusplus)
    #define NULL 0
    #else
    #define NULL ((void *)0)
    #endif

    可以看出c++中 NULL为(int)0 , C中NULL为(void*)0

     

    MyString.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    #ifndef _MyString_H_
    #define _MyString_H_

    #include <iostream>

    /*MyString对外的接口*/
    class MyString
    {
    public:
        MyString(char *pData = NULL);
        MyString(const MyString &str);
        ~MyString(void);

        MyString &operator = (const MyString &str);

        void Print();

    private:
        char *m_pData;
    };

    #endif /*_MyString_H_*/
     
    MyString.cpp
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
     
    #include "MyString.h"
    #include <iostream>
    #include <cstring>

    using namespace std;

    /*构造函数*/
    MyString::MyString(char *pData)
    {
        if(pData == NULL)
        {
            m_pData = new char[1];
            m_pData[0] = '';
        }
        else
        {
            int length = strlen(pData);
            m_pData = new char[length + 1];
            strcpy(m_pData, pData);
        }
    }

    /*拷贝构造函数*/
    MyString::MyString(const MyString &str)
    {
        int length = strlen(str.m_pData);
        m_pData = new char[length + 1];
        strcpy(m_pData, str.m_pData);
    }

    /*析构函数*/
    MyString::~MyString()
    {
        delete[] m_pData;
    }

    /*等号运算符的重载*/
    /*MyString& MyString::operator = (const MyString& str)
    {
        if(this == &str)
            return *this;

        delete []m_pData;
        m_pData = NULL;

        m_pData = new char[strlen(str.m_pData) + 1];
        strcpy(m_pData, str.m_pData);

        return *this;
    }*/


    MyString &MyString::operator = (const MyString &str)
    {
        if(this != &str)
        {
            /*调用拷贝构造函数*/
            MyString temp(str);

            char *pTemp = temp.m_pData;
            temp.m_pData = m_pData;
            m_pData = pTemp;
        }
        return *this;
    }

    /*打印函数*/
    void MyString::Print()
    {
        cout << m_pData;
    }

     

    程序运行到if的外面就除了temp这个变量的作用域,就会自动调用temp的析构函数,把temp.m_pData所指向的内存释放掉。这个指针指向的内存就是实例之前m_pData的内存。

    main.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
     
    #include "MyString.h"

    #include <iostream>
    #include <string.h>

    using namespace std;

    /*赋值操作*/
    void Test1()
    {
        cout << "Test1 begins:" << endl;

        /*注意这个指针指向常量区*/
        /* 警告: 不建议使用从字符串常量到‘char*’的转换---这里会有警告*/
        char *text = "Hello world";
        MyString str1(text);
        MyString str2;
        str2 = str1;

        cout << "The expected result is: " << text << endl;
        cout << "The actual result is: ";
        str2.Print();
        cout << endl;
    }

    // 赋值给自己
    void Test2()
    {
        cout << "Test2 begins:" << endl;

        /*注意这个指针指向常量区*/
        char *text = "coding my life";
        MyString str1(text);
        str1 = str1;

        cout << "The expected result is: " << text << endl;
        cout << "The actual result is: ";
        str1.Print();
        cout << endl;
    }

    // 连续赋值
    void Test3()
    {
        cout << "Test3 begins:" << endl;

        char *text = "Hello world boy!";

        MyString str1(text);
        MyString str2, str3;
        str3 = str2 = str1;

        cout << "The expected result is: " << text << endl;
        cout << "The actual result is: ";
        str2.Print();
        cout << endl;

        cout << "The expected result is: " << text << endl;
        cout << "The actual result is: ";
        str3.Print();
        cout << endl;
    }

    int main(int argc, char *argv[])
    {
        Test1();
        Test2();
        Test3();

        return 0;
    }
    Makefile
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    .PHONY:clean  
    CPP=g++  
    CFLAGS=-Wall -g  
    BIN=test  
    OBJS=main.o MyString.o  
    LIBS=  
    $(BIN):$(OBJS)  
        $(CPP) $(CFLAGS) $^ -o $@ $(LIBS)  
    %.o:%.c  
        $(CPP) $(CFLAGS) -c $< -o $@  
    clean:  
        rm -f *.o $(BIN)  

    测试环境是Ubuntu 12.04.2 LTS

  • 相关阅读:
    合肥程序员欢迎进QQ群49313181同城程序员职业交流群
    新一代程序员的思考
    ThinkPHP开发系列一框架搭建
    ASP.NET MVC4+EF系列之五 架构介绍
    ASP.NET MVC4+EF系列之阶段源码一
    gcc g++ Linux下动态库_静态库 指定路径问题
    [转]accept() 产生的Socekt端口是多少?
    阿里云计算资深总监唐洪:飞天大规模分布式计算系统解析
    [转] C++中##(两个井号)和#(一个井号)用法
    deep learning 深度学习
  • 原文地址:https://www.cnblogs.com/codemylife/p/3657304.html
Copyright © 2011-2022 走看看