变量的定义为变量分配地址和存储空间,变量的声明不分配地址。 一个变量可以在多个地方声明,但只能在一个地方定义。 加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 结构可以直接赋值吗?

c语言判断是否为素数_c 判断是否为null_c++判断字符串是否为空

可以在声明的时候直接初始化,也可以在同一个结构体的不同对象之间直接赋值,但是在结构体中包含指针“成员”时一定要小心。

【注意】:当有多个指针指向同一段内存时,一个指针释放该段内存可能会导致其他指针的非法操作。 因此,在释放这块内存空间之前,必须确保其他指针不再使用它。

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”

c++判断字符串是否为空_c语言判断是否为素数_c 判断是否为null

“坏演员”

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),注意两个函数体的语句不同,实现的功能不同。

c++判断字符串是否为空_c 判断是否为null_c语言判断是否为素数

【注意】模板及其特化版本应在同一个头文件中声明,所有同名模板的声明放在前面,特化版本后跟。

(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