变量的定义为变量分配地址和存储空间,变量的声明不分配地址。 一个变量可以在多个地方声明,但只能在一个地方定义。 加extern修饰是变量的声明,表示变量将定义在文件外或文件后部。
解释:在很多情况下,一个变量只是声明不分配内存空间,直到具体使用时才进行初始化,分配内存空间,比如外部变量。
int main()
{
extern int A;
//这是个声明而不是定义,声明A是一个已经定义了的外部变量
//注意:声明外部变量时可以把变量类型去掉如:extern A;
dosth(); //执行函数
}
int A; //是定义,定义了A为整型的外部变量
2 简述#ifdef、#else、#endif和#ifndef的作用
使用#ifdef、#endif来包含一个程序功能模块,为特定用户提供该功能。 用户可以在不需要时轻松禁用它。
#ifdef MATH
#include "math.c"
#endif
标记子程序以便于跟踪和调试。
#ifdef DEBUG
printf ("Indebugging......!");
#endif
解决硬件限制。 因为一些具体应用环境的硬件不同,受条件限制,本地没有这种设备,所以只能绕过硬件,直接写预期结果。
【注意】:虽然不用条件编译命令,直接使用if语句也可以满足需求,但是目标程序会很长(因为所有语句都被编译),运行时间也很长(因为if语句是在测试过程中测试的)程序运行时间)。 使用条件编译可以减少需要编译的语句数c++判断字符串是否为空,从而减少目标程序的长度,减少运行时间。
3 编写用于比较 int、bool、float 和指针变量与“零值”的 if 语句
//int与零值比较
if ( n == 0 )
if ( n != 0 )
//bool与零值比较
if (flag) // 表示flag为真
if (!flag) // 表示flag为假
//float与零值比较
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON) //其中EPSINON是允许的误差(即精度)。
//指针变量与零值比较
if (p == NULL)
if (p != NULL)
4 结构可以直接赋值吗?
可以在声明的时候直接初始化,也可以在同一个结构体的不同对象之间直接赋值,但是在结构体中包含指针“成员”时一定要小心。
【注意】:当有多个指针指向同一段内存时,一个指针释放该段内存可能会导致其他指针的非法操作。 因此,在释放这块内存空间之前,必须确保其他指针不再使用它。
5 sizeof和strlen的区别 6 C语言中的static关键字和C++中的static关键字有什么区别
在C语言中,static用于修饰局部静态变量和外部静态变量和函数。 除了上述函数,C++还用于定义类的成员变量和函数。 即静态成员和静态成员函数。
【注意】:编程时static的内存和全局特性允许在不同时间调用的函数进行信息传递和传递,而C++静态成员可以在多个对象实例之间进行传递和传递信息。
7 C语言中的malloc和C++中的new有什么区别
【注意】:malloc申请的内存空间要free释放,new申请的内存空间要delete释放,不要混用。
8 编写“标准”宏 MIN
#define min(a,b)((a)pb_->print(); 英文pb_是一个weak_ptr,要先转换成shared_ptr,如:shared_ptr p = pa->pb_.lock(); p- >打印();
39 浅谈cast运算符
“static_cast”
“dynamic_cast”
“const_cast”
“坏演员”
bad_cast 使用
try {
Circle& ref_circle = dynamic_cast(ref_shape);
}
catch (bad_cast b) {
cout << "Caught: " << b.what();
}
40 谈谈你对拷贝构造函数和赋值运算符的理解
拷贝构造函数和赋值运算符重载有两个区别:
【注意】:当类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认的。
41 C++中,malloc申请的内存能否通过delete释放? new申请的内存可以免费使用吗?
不是,malloc/free主要是为了兼容C,new和delete可以完全替代malloc/free。 malloc/free 的操作对象必须有明确的大小。 它不能用于动态类。 new和delete会自动进行类型检查和size,malloc/free不能执行构造函数和析构函数,所以动态对象是不行的。 当然,理论上malloc申请的内存可以通过delete释放。 但是一般不会这样写。 并且不能保证每个 C++ 运行时都能正常工作。
42 在C++中设计一个不能继承的类
template class A
{
friend T;
private:
A() {}
~A() {}
};
class B : virtual public A
{
public:
B() {}
~B() {}
};
class C : virtual public B
{
public:
C() {}
~C() {}
};
void main( void )
{
B b;
//C c;
return;
}
【注】:构造函数是实现继承的关键。 每次构造子类对象时,先调用父类的构造函数,再调用自己的构造函数。
43 C++自己实现一个String类
#include
#include
using namespace std;
class String{
public:
// 默认构造函数
String(const char *str = nullptr);
// 拷贝构造函数
String(const String &str);
// 析构函数
~String();
// 字符串赋值函数
String& operator=(const String &str);
private:
char *m_data;
int m_size;
};
// 构造函数
String::String(const char *str)
{
if(str == nullptr) // 加分点:对m_data加NULL 判断
{
m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志''的
m_data[0] = '';
m_size = 0;
}
else
{
m_size = strlen(str);
m_data = new char[m_size + 1];
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(const String &str) // 得分点:输入参数为const型
{
m_size = str.m_size;
m_data = new char[m_size + 1]; //加分点:对m_data加NULL 判断
strcpy(m_data, str.m_data);
}
// 析构函数
String::~String()
{
delete[] m_data;
}
// 字符串赋值函数
String& String::operator=(const String &str) // 得分点:输入参数为const
{
if(this == &str) //得分点:检查自赋值
return *this;
delete[] m_data; //得分点:释放原有的内存资源
m_size = strlen(str.m_data);
m_data = new char[m_size + 1]; //加分点:对m_data加NULL 判断
strcpy(m_data, str.m_data);
return *this; //得分点:返回本对象的引用
}
44 访问基类的私有虚函数
编写以下程序的输出:
#include
class A
{
virtual void g()
{
cout << "A::g" << endl;
}
private:
virtual void f()
{
cout << "A::f" << endl;
}
};
class B : public A
{
void g()
{
cout << "B::g" << endl;
}
virtual void h()
{
cout << "B::h" << endl;
}
};
typedef void( *Fun )( void );
void main()
{
B b;
Fun pFun;
for(int i = 0 ; i < 3; i++)
{
pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i );
pFun();
}
}
输出结果:
B::g
A::f
B::h
《注意》:考察面试官对虚函数的理解。 一个不懂虚函数的人很难把这道题做对。 在学习面向对象多态的时候,一定要深刻理解虚函数表的工作原理。
45 虚函数和多态的理解
多态的实现主要分为静态多态和动态多态。 静态多态主要是重载,在编译时就已经确定了; 动态多态是通过虚函数机制实现的,在运行时动态绑定。 例如:当父类类型的指针指向子类对象时,当使用父类的指针调用子类中重写的父类中的虚函数时,就会调用子类重写的函数。 在父类中声明为加了virtual关键字的函数,在子类中重写时也是不加virtual的虚函数。
虚函数的实现:在一个有虚函数的类中,类的第一部分是一个指向虚函数表的指针。 这个指针指向一个虚函数表,虚函数的地址放在表中。 实际的虚函数在代码段(.text)中。 当子类继承父类时,也会继承它的虚函数表。 当子类重写父类中的虚函数时,会将其继承的虚函数表中的地址替换为重写后的函数地址。 使用虚函数会增加内存访问开销,降低效率。
46 简述类成员函数的重写、重载和隐藏的区别
(1)重写和重载主要有以下几点不同。
(2) 隐藏与重写和重载的区别在于以下几点。
《注》:虽然重载和覆盖都是多态的基础,但两者实现的技术完全不同,达到的目标也完全不同。 覆盖是动态和动态绑定的多态,而重载是静态绑定的多态。
47 链表和数组有什么区别
【注意】:在选择数组或链表数据结构时,一定要根据实际需要来选择。 数组易于查询,链表易于插入和删除。 数组节省空间但长度固定,链表虽然变长但占用更多存储空间。
48 使用两个栈实现一个队列的功能
typedef struct node
{
int data;
node *next;
}node,*LinkStack;
//创建空栈:
LinkStack CreateNULLStack( LinkStack &S)
{
S = (LinkStack)malloc( sizeof( node ) ); // 申请新结点
if( NULL == S)
{
printf("Fail to malloc a new node.n");
return NULL;
}
S->data = 0; //初始化新结点
S->next = NULL;
return S;
}
//栈的插入函数:
LinkStack Push( LinkStack &S, int data)
{
if( NULL == S) //检验栈
{
printf("There no node in stack!");
return NULL;
}
LinkStack p = NULL;
p = (LinkStack)malloc( sizeof( node ) ); // 申请新结点
if( NULL == p)
{
printf("Fail to malloc a new node.n");
return S;
}
if( NULL == S->next)
{
p->next = NULL;
}
else
{
p->next = S->next;
}
p->data = data; //初始化新结点
S->next = p; //插入新结点
return S;
}
//出栈函数:
node Pop( LinkStack &S)
{
node temp;
temp.data = 0;
temp.next = NULL;
if( NULL == S) //检验栈
{
printf("There no node in stack!");
return temp;
}
temp = *S;
if( S->next == NULL )
{
printf("The stack is NULL,can't pop!n");
return temp;
}
LinkStack p = S ->next; //节点出栈
S->next = S->next->next;
temp = *p;
free( p );
p = NULL;
return temp;
}
//双栈实现队列的入队函数:
LinkStack StackToQueuPush( LinkStack &S, int data)
{
node n;
LinkStack S1 = NULL;
CreateNULLStack( S1 ); //创建空栈
while( NULL != S->next ) //S 出栈入S1
{
n = Pop( S );
Push( S1, n.data );
}
Push( S1, data ); //新结点入栈
while( NULL != S1->next ) //S1 出栈入S
{
n = Pop( S1 );
Push( S, n.data );
}
return S;
}
【注】:一个队列的功能可以用两个栈来实现,那么一个队列的功能可以用两个队列来实现吗? 结果是否定的,因为栈是先进先出的,两个栈连在一起就是先进先出。 队列是先进先出的,不管多少个连在一起都是先进先出,不可能做到先进后出。
第49章模板函数和模板类的特化
《介绍理由》
写一个单一的模板,可以适应多种类型的需求,让每个类型都有相同的功能,但是对于一个特定的类型,如果要实现它特有的功能,单一的模板是做不到的,那么就需要模板专业化
“定义”提供了一个单一模板的特殊实例,它将一个或多个模板参数绑定到一个特定的类型或值
(1) 模板函数特化
原函数模板的每个模板参数都必须提供一个实参c++判断字符串是否为空,关键字template后跟一对空尖括号用来表示原模板的所有模板参数都提供了实参,例如如下:
template //模板函数
int compare(const T &v1,const T &v2)
{
if(v1 > v2) return -1;
if(v2 > v1) return 1;
return 0;
}
//模板特例化,满足针对字符串特定的比较,要提供所有实参,这里只有一个T
template
int compare(const char* const &v1,const char* const &v2)
{
return strcmp(p1,p2);
}
“基本”专业化的本质是实例化模板,而不是重载它。 专业化不影响参数匹配。 参数匹配基于最佳匹配原则。 比如这里如果是compare(3,5),就调用普通的模板,如果是compare("hi", "haha"),就调用特化版(因为这个cosnt char*匹配实参好于T Type),注意两个函数体的语句不同,实现的功能不同。
【注意】模板及其特化版本应在同一个头文件中声明,所有同名模板的声明放在前面,特化版本后跟。
(2)类模板特化
原理和函数模板类似,但是在类中,我们可以特化模板或者部分特化类。 特化一个类时,仍然使用template来表示它是一个特化版本,例如:
template
class hash
{
size_t operator()(sales_data& s);
//里面所有T都换成特例化类型版本sales_data
//按照最佳匹配原则,若T != sales_data,就用普通类模板,否则,就使用含有特定功能的特例化版本。
};
“类模板的部分专业化”
不必为所有模板参数都提供实际参数,可以指定一些但不是全部模板参数。 类模板的部分特化仍然是模板本身。 使用它时,它还必须为其特化版本中未指定的模板参数提供实际参数。 (特化时,类名必须和原模板相同,但参数类型不同,根据最佳匹配原则,匹配最好的,使用对应的模板)
“某专业班的部分成员”
你可以特化一个类中的一些成员函数而不是整个类,例如:
template
class Foo
{
void Bar();
void Barst(T a)();
};
template
void Foo::Bar()
{
//进行int类型的特例化处理
cout << "我是int型特例化" << endl;
}
Foo fs;
Foo fi;//使用特例化
fs.Bar();//使用的是普通模板,即Foo::Bar()
fi.Bar();//特例化版本,执行Foo::Bar()
//Foo::Bar()和Foo::Bar()功能不同
50 为什么析构函数一般写成虚函数?
由于类的多态性,基类指针可以指向派生类的对象。 如果删除了基类的指针,就会调用该指针指向的派生类的析构函数,派生类的析构函数会自动调用基类。 析构函数,使派生类的整个对象完全释放。 如果析构函数未声明为虚函数,编译器将实现静态绑定。 删除基类指针时,只会调用基类的析构函数,不会调用派生类的析构函数,这样会导致派生类的对象析构不完整,从而导致内存泄漏。 所以有必要将析构函数声明为虚函数。 实现多态时,在使用基类操作派生类时,为防止析构时只析构基类而未析构派生类的情况,应将基类的析构函数声明为虚函数。 例如:
#include
using namespace std;
class Parent{
public:
Parent(){
cout << "Parent construct function" << endl;
};
~Parent(){
cout << "Parent destructor function" <<endl;
}
};
class Son : public Parent{
public:
Son(){
cout << "Son construct function" << endl;
};
~Son(){
cout << "Son destructor function" <<endl;
}
};
int main()
{
Parent* p = new Son();
delete p;
p = NULL;
return 0;
}
//运行结果:
//Parent construct function
//Son construct function
//Parent destructor function