【面试题一】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数
2013-11-30 10:49
253 查看
一,在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.把返回值的类型声明为该类型的引用,并且返回自身的引用*this,这样子才可以做连续赋值。
2.把传入的类型声明为常量的引用。如果是值传递的话会调用拷贝构造函数,这样子可以避免无所谓的消耗。同时我们在赋值运算符函数中不会改变传入的实例的状态,因此应该用const修饰。
3.分配新的内存之前需要释放实例自身已有的内存。否者程序将出现内存泄漏。
4.判断传入的参数和当前实例(*this)是不是同一个实例,如果是同一个不必进程赋值操作,如果没有判断,释放了自身的内存的时候会导致严重的问题。
写代码的过程中发现了一个问题 NULL在那个头文件中的问题:
NULL定义在stddef.h中,isstream与vector通过包含cstdef包含了stddef.h
定义如下
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
可以看出c++中 NULL为(int)0 , C中NULL为(void*)0
MyString.h
MyString.cpp
程序运行到if的外面就除了temp这个变量的作用域,就会自动调用temp的析构函数,把temp.m_pData所指向的内存释放掉。这个指针指向的内存就是实例之前m_pData的内存。
main.cpp
Makefile
测试环境是Ubuntu 12.04.2 LTS
好多书籍,推荐使用类型转换的关键字,但是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类声明,请为该类型添加赋值运算符函数,
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
定义如下
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
可以看出c++中 NULL为(int)0 , C中NULL为(void*)0
MyString.h
#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
#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] = '\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
#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
.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
相关文章推荐
- 【面试题001】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数
- 【面试题001】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数
- 【面试题001】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数
- 【面试题001】类型转换关键字,空类对象模型,拷贝构造函数,赋值运算符函数
- java(instanceof操作符、对象类型转换 、final 关键字、final,finally,finalize的区别、static关键字,修饰符 )
- C++ 操作符重载、函数对象及类类型转换
- 【自用】Javanote170801(封装、多态、instanceof运算符、对象转换类型、final关键字、参数传递)
- c++对象模型笔记:指针类型转换
- 【面向对象程序设计常见面试题】赋值运算符和拷贝构造函数的区别与联系?(3)
- 丶使用as关键字将对象转换为指定类型
- javascript——对象的概念——函数 2 (内建函数与类型转换)
- DirectX 3D_基础之HLSL(高级着色语言) HLSL着色器程序的编制 HSLS变量 HLSL入口函数 HLSL程序编译 变量常量类型 设置方法 前缀 关键字 类型 语句 类型转换
- C++ Pirmer : 第十四章 : 重载运算符与类型转换之函数调用运算符与标准库的定义的函数对象
- C++ Primer学习笔记——$14 操作符重载、函数对象及类类型转换
- 深入理解C++对象模型之类型转换:ReinterpretCast
- 【你不知道】表达式中的隐式类型转换、无名对象作为函数实参
- 面题1(类型转换关键字、sizeof(空类型)、复制构造、赋值运算符)
- C++ Pirmer : 第十五章 : 面向对象程序设计之基类和派生的定义、类型转换与继承与虚函数
- 【面试题】c++有哪四个类型转换相关的关键字?
- 《剑指offer》面试题1:为类CMyString添加赋值运算符函数——C++拷贝构造函数与赋值函数