C/C++编程细节(五)——重要知识点

1. 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“ 别名 ”(alias),对应用的操作与对变量直接操作效果完全相同。 申明一个引用的时候,   切记要对其进行初始化 。  引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名, 不能再把该引用名作为其他变量名的别名  。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种 数据类型,因此引用本身不占存储单元,系 统也不给引用分配存储单元  。 不能建立数组的引用。

2. 将“引用”作为函数参数有哪些特点?
(1) 传递引用给函数与传递指针的效果是一样的  。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数 中)的操作。
(2) 使用引用传递函数的参数,在 内存中并没有产生实参的副本,它是直接对实参操作  ;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因 此,当参数传递的数据较大时, 用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引 用的效果,但是,在被调函数中 同 样要给形参分配存储单元  ,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面, 在 主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

3. 在什么时候需要使用“常引用”? 
如果既要利用引用提高程序的效率,又要保护传递给函 数的数据不在函数中被改变,就应使用常引用。常引用声明方式: const 类型标识符 &引用名=目标变量名;
例1
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确

例2
void bar(string& s);
bar("hello");  //错误 ==> 要改成: string s="hello"; bar(s);
原因在于"hello"串会产生一个临时对象,而在 C++中, 临时对象都是const类型的 。因此上面的表达式就是试图将 一个const类型的对象转换为非 const类型,这是非法的。

void bar(const string& s);
bar("hello");  //正确
引用型参数应该在能被定义为const的情况下,尽量定义为const 。

4. 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回值的副本;(注意:正是 因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:
(1) 不能返回局部变量的引用  。这条可以参照 Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2) 不能返回函数内部new分配 的内存的引用  。这 条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放, 造成memory leak  。
(3) 可以返回类成员的引用,但最 好是const  。 这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

5. “引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。这意 味着,一个基类的引用可以指向它的派生类实例。
例4
Class A; Class B : Class A{...};  B b;  A& ref = b;

6. “引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它所指向的 变量间接操作。程序中使用指针,程序 的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。

7. 结构与联合有和区别?  
1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。 
2. 对于联合的不同成员赋值, 将会对其它成员重写,  原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

8. .h头文件中的ifndef/define/endif 的作用?
答:防止该头文件被重复引用。

9. #include<file.h> 与 #include "file.h"的区别?
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。

10.面向对象的三个基本特征,并简单叙述之?
1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承 (仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是 一句话: 允许将子类类型的指针赋值给父类类型的指针

11. New delete 与malloc free 的联系与区别?
答 案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.


12. #define DOUBLE(x) x+x , i = 5*DOUBLE(5); i 是多少?
答 案:i 为30。 (5*5+5) = 30
13. C++是不是类型安全的?  
答案:不是。两个不同类型的指针之间可以强制 转换(用reinterpret cast)。C#是类型安全的。
14. 描述内存分配方式以及他们的区别
a) 从 静态存储区域  分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
b) 在  上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令 集。
c) 从  上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

14.分别写出 BOOL,int,float,指针类型的变量a 与“零”的比较语句。  
答 案:
BOOL :    if ( !a ) or if(a)
int :     if ( a == 0)
float :   const EXPRESSION EXP = 0.000001
          if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)


15.简述数组与指针的区别?  
数组要么在静态存储 区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别
      char a[] = “hello”;
      a[0] = ‘X’;
      char *p = “world”; // 注意p 指向常量字符串
      p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。 sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量  。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。  注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的 指针。
      char a[] = "hello world";
      char *p = a;
      cout<< sizeof(a) << endl; // 12 字节
      cout<< sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}


16. 如何打印出当前源文件的文件名以及源文件的当前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定 义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。

17. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的?  
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
18.  const 符号常量; 
(1)const char *p
(2)char const *p
(3)char * const p
说明上面三种描述的区别
(1)const char *p
(2)char const *p
(3)char * const p
说明上面三种描述的区别
答案:
const char *p; //和char const *p一样
char const * p;//指向常量的指针,指向的常量值不可以改
char * const p; //常量指针,p的值不可以修改
19.下面是C语言中两种if语句判断方式。请问哪种写法更好?为什么?  
 int n;
 if (n == 10) // 第一种判断方式
 if (10 == n) // 第二种判断方式
第二种, 如果少了个=号,编译时就会报错,减少了出错的可能行,可以检测出是否少了=

20. 下面的代码有什么问题?  
void DoSomeThing(...)
{
 char* p;
 ...
 p = malloc(1024);  // 分配1K的空间
 if (NULL == p)
  return;
 ...
 p = realloc(p, 2048); // 空间不够,重新分配到2K
 if (NULL == p)
  return;
 ...
}
答案:
1. p = malloc(1024);   应该写成: p = (char *) malloc(1024); 
2. 没有释放p的空间,造成内存泄漏。
21.下面代码有什么错误?  
Void test1() 

 char string[10]; 
 char *str1="0123456789";
 strcpy(string, str1); 
}
数组越界。 strcpy拷贝的结束标志是查找字符串中的/0 因此如果字符串中没有遇到/0的话 会一直复制,直到遇到/0,上面的123都因此产生越界的情况,建议使用 strncpy 和 memcpy
22.进程间通信的方式有?
进程间通信的方式有 共享内存, 管道 ,Socket ,消息队列 , DDE等

23.下面的函数实现在一个固定的数上加上一个数,有什么错误,改正  
int add(int n) 

  static int i=100; 
  i+=n; 
  return i; 
}
add(10); //output 110
add(20); //output 130(110+20), instead of 120, just because static i will keep the last value(110)
答:
因为static使得i的值会保留上次的值。去掉static就可了
24.下面的代码有什么问题?  
class A 

public: 
  A() { p=this; } 
  ~A() { if(p!=NULL) { delete p; p=NULL; } }
  A* p; 
};
答:
会引起无限递归, 析构的时候, delete p等价于delete this, 然后this又赋值给了p ...
25.生僻C++关键字汇总
a) volatile
当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器 中。如 果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。 volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。
b) mutable
mutable关键字提示编译器该类成员变量可以被类的const函数修改(通常类的const函数不能修改类成员变量)
c) explicit
explicit关键字用于取消构造函数的隐式转换, 对有多个参数的 构造函数使用explicit是个语法错误。
如果c++类的构造函数有一个参数,那么在编译的时候就会有一个缺省的转换操作(隐式转换):将该构造函数对应数据类型的数据转换为该类对象,如下 面所示:
class MyClass
{
public:
      MyClass( int num );
}
....
MyClass obj = 10;   //ok,convert int to MyClass
在上面的代码中编译器自动将整型转换为MyClass类对象,实际上等同于下面的操作:
MyClass temp(10);
MyClass obj = temp;
上面的所有的操作即是所谓的"隐式转换".
如果要避免这种自动转换的功能,我们该怎么做呢?嘿嘿这就是关键字explicit的作用了,将类的构造函数声明为"显示",也就是在声明构造函数 的时候 前面添加上explicit即可,这样就可以防止这种自动的转换操作,如果我们修改上面的MyClass类的构造函数为显示的,那么下面的代码就不能够编 译通过了,如下所示:
class MyClass
{
public:
     explicit MyClass( int num );
     MyClass(float num);
}
....
MyClass obj = 10; //Error,can't non-explict convert   必须要写成 MyClass obj = MyClass(10); 主要是为了代码结构清晰.
MyClass obj2 = 1.0f;  //Okay, MyClass(float num) is not explict.
d) auto
它是存储类型标识符,表明变量(自动)具有本地范围,块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型。
e) register
用register声明的变量称着寄存器变量,在可能的情况下会直接存放在机器的寄存器中;但对32位编译器不起作用,当global optimizations(全局优化)开的时候,它会做出选择是否放在自己的寄存器中;不过其它与register关键字有关的其它符号都对32位编译 器有效。
26.程序改错  
class mml
{
  public:
    mml(){}
    ~mml{}                   
};
class nnl:public mml
{
  public:
    nnl(){}
    ~nnl{}             
};
代码片断:
mml* pp = new nnl();
..........
delete pp;

答案:
基类的析构函数应该为虚函数 virtual ~mml{} ,否则delete pp的时候只析构了mml类的对象,nnl类的对象没有被删除


27. 已知String类定义如下:
class String
{
public:
String(const char *str = NULL); // 通用构造函数
String(const String &another); // 拷贝构造函数
~ String(); // 析构函数
String & operater =(const String &rhs); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
尝试写出类的成员函数实现。
答案:
String::String(const char *str)
{
   if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
     {
       m_data = new char[1] ;
       m_data[0] = '/0' ;
     }
   else
    {
       m_data = new char[strlen(str) + 1];
       strcpy(m_data,str);
    }

String::String(const String &another)
{
    m_data = new char[strlen(another.m_data) + 1];  //deep copy
    strcpy(m_data,other.m_data);
}

String& String::operator =(const String &rhs)
{
    if ( this == &rhs)
        return *this ;
    delete []m_data; //删除原来的数据,新开一块内存
    m_data = new char[strlen(rhs.m_data) + 1];
    strcpy(m_data,rhs.m_data);
    return *this ;

}


String::~String()

{

    delete []m_data ;

}

你可能感兴趣的:(C++,volatile,引用,Explicit)