首页 > 技术文章 > (3.2)狄泰软件学院C++课程学习剖析三

wycBlog 2017-07-31 16:40 原文

对课程前面40课的详细回顾分析(一)

0、

  • int main()
  • {
  • // ① Array t(3,3); //普通模式
  • // ② Array *t=new Array(3,3); //指针方式
  • // cout<<t->a<<endl;
  • //③ Array t=Array(3,3); //临时对象
  • //cout<<t.a<<endl;
  • int b=3;
  • int *p=&b;
  • int *a=new int(b); //在堆空间申请一个int 里面存的值为b
  • cout<<p<<endl<<a<<endl;
  • return 0;
  • }

1、在最初语言规划的阶段,C语言的出现纯粹是为了编写unix操作系统;造成了C语言没有太多深思熟虑的过程,遗留了太多低级语言的特征; 进而造成了软件的可维护性和可重用性差。

c++中的register关键字对于C语言只是一个兼容性问题,在c++中遇到register关键字的时候会自动忽略掉;在c++中的任意标识符都必须显式的指明类型 。

 

 

 

 2、

C语言中的const修饰的变量(无论是全局变量还是局部变量都只是只读的,本质上还是变量,不是真正意义上的常量;只在编译期间有用,在运行时无用,所以我们可以骗过编译器对一个const修饰的变量进行修改)

骗过编译器修改const修饰变量的的方法:

  1. int main()
  2. {
  3. const int a=5;
  4. int *p=(int *)&a;
  5. *p=4;
  6. printf("a=%d\n *p=%d\n",a,*p);
  7. return 0;
  8. }

但是在c++中能够定义真正意义上的常量,是不能够修改的。

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。

 

  1. int main()
  2. {
  3. int a=5;
  4. int *p1=&a;
  5. int &b=a;       //引用
  6. b=4;
  7. int *p2=&b;
  8. printf("a=%d\n",a);
  9. printf("%p\n%p\n",*p1,*p2);
  10. return 0;
  11. }

int * p
(1)const int *p
(2)int const *p
(3)int * const p
(4)const int *const p

引用和指针的区别和联系:

★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,
但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;

3、c++中可以使用const常量代替宏常数定义;同样我们可以使用内联函数来替代宏代码片段。内联函数在声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。c++编译器可以将一个函数内联编译,所谓的内联编译就是编译器直接将函数体插入到函数调用的地方,类似于宏定义替换;内联函数省去了普通函数调用时的额外开销(压栈、跳转、返回);但是inline只是一种请求,c++编译器对于函数的内联请求不一定都会满足。

4、c++可以在函数声明时为参数提供一个默认值,当函数调用时没有提供参数时候则使用默认值;尤其需要指出的一点是参数的默认值必须在函数声明中指定;当函数声明和函数定义中都有一个默认值时候则选用函数声明中的默认值,同时可以在函数参数中使用占位参数,占位参数只有函数参数类型声明,而没有参数名。

5、c++中int function()和int function(void)没有区别,都表示无参数接收,返回值为int的函数;但是在C语言中前者表示可以接收任意参数,后者才表示不接受参数。

6、重载函数本质上是一个个相互独立的不同的函数,函数重载是由函数名和参数列表决定的,返回值不能作为判断依据。

 

extern关键字可以实现C语言和c++的相互调用。函数重载在c++中才可以,在C语言中不行。

7、new关键字

8、

 

const引用其实就是const int * const p,生成一个新的只读变量。

const int &a=b;也是一个只读变量

volatile const int &a;也是一个只读变量

const int &a=5;//常量

9、c++中的强制类型转换

 (1)dynamic_cast操作符:用于基类和派生类之间的类指针或者类引用的转换,基类中必须要有虚函数。

用法:

dynamic_cast <type-id> (expression)
该运算符把expression转换成type-id类型的对象。Type-id 必须是类的指针、类的引用或者void*
如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
dynamic_cast运算符可以在执行期决定真正的类型。如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果 downcast 不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
  1. classB
  2. {
  3. public:
  4. int m_iNum;
  5. virtual void foo();
  6. };
 
  1. classD:publicB
  2. {
  3. public:
  4. char* m_szName[100];
  5. };
 
  1. void func(B* pb)
  2. {
  3. D* pd1=static_cast<D*>(pb);
  4. D* pd2=dynamic_cast<D*>(pb);
  5. }
 
在上面的代码段中,如果 pb 指向一个 D 类型的对象,pd1 和 pd2 是一样的,并且对这两个指针执行 D 类型的任何操作都是安全的;但是,如果 pb 指向的是一个 B 类型的对象,那么 pd1 将是一个指向该对象的指针,对它进行 D 类型的操作将是不安全的(如访问 m_szName),而 pd2 将是一个空指针。
另外要注意:B 要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

交叉转换

编辑
另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示:
  1. classA
  2. {
  3. public:
  4. intm_iNum;
  5. virtual void f(){}
  6. };
  7. class B:public A
  8. {
  9. };
  10. class D:public A
  11. {
  12. };
  13. void foo()
  14. {
  15. B*pb=newB;
  16. pb->m_iNum=100;
  17. //D*pd1=static_cast<D*>(pb);//compile error
  18. D*pd2=dynamic_cast<D*>(pb);//pd2isNULL
  19. delete pb;
  20. }
 
 
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错,而使用 dynamic_cast的转换则是允许的,结果是空指针。

(2)const_cast操作符:用于去除变量的const只读属性,强制转换的目标类型必须是指针或者引用。

 

  1. int main()
  1. {
  1. const int a=3;
  1. int *p=const_cast<int *>(&a);
  1. *p=5;
  1. cout<<*p<<endl; //*p=5
  1. return 0;
  1. }

 

(3)static_cast操作符:可以把它完全当作C语言中的暴力强制类型转换,该操作符用于非多态类型的转换,任何标准转换都可以使用,即static_cast可以把int转换为double,但不能把两个不相关的类对象进行转换,比如类A不能转换为一个不相关的类B类型,但是 对于两个相关的类转换其类对象是可以的。static_cast本质上是传统c语言强制转换的替代品。

(4)reinterpret_cast操作符:用于指针类型间的类型转换 ,用于整数和指针之间的类型转换。

 

代码示例:

  1. int main()
  2. {
  3. int a=3;
  4. int *p=&a;
  5. cout<<p<<endl;
  6. double b=3.90;
  7. double *p1=&b;
  8. cout<<p1<<endl;
  9. p1=reinterpret_cast<double *>(p);
  10. // p1=(double *)p; //c语言的转换方法
  11. cout<<p1<<endl;
  12. return 0;
  13. }

 

10、深究引用(引用和指针的关系深究) 

对于引用变量的内部实现,可以得出如下结论:
引用变量:
1)引用的内部实现为相当于一个指针常量    int *const p ,与指针的实现方式类似;
2)引用变量内存单元保存的指向变量地址(初始化时赋值),与指针不同地方时,引用变量在定义时必须初始化,而且使用过程中,引用变量保存的内存单元地址值是不能改变的(这一点通过编译器来实现保证);
3)引用也可以进行取地址操作,但是取地址操作返回的不是引用变量所在的内存单元地址,而是被引用变量本身所在的内存单元地址;(对引用变量的取地址操作相当于取内容操作,如果要想取得引用变量的地址,应使用两次取地址符号,如:&(&a) )
4)引用的使用,在源代码级相当于普通的变量一样使用,但在函数参数传递引用变量时,内部传递的实际是变量的地址值(这种机制的实现是通过编译器(编译手段)来实现的)。

  不能将引用简单理解为变量的代记符号,引用本身是通过指针实现,并且占用相应的内存空间。

 参考博客:http://blog.csdn.net/thisispan/article/details/7456169

11、重载、重写和多态的区别?

c++中的多态性本质上是为了接口重用,体现在函数重写上;而函数重载只发生在一个类中,不像重写那样在基类和子类中,所以不算是多态性的体现。

重定义和重写虽然都发生在基类和派生类之间,但是重写是对基类的虚函数进行重写(函数名,参数列表都需要跟基类的一致),而重定义是对基类的非虚成员函数进行函数定义,

函数 的参数列表不用跟基类的保持一致。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

12、类之间的基本关系:组合和继承。组合是has-a,整体和局部的关系;继承是is-a的关系,子类拥有父类的全部属性和方法。

13、一个类中可以存在多个重载的构造函数,构造函数的重载依靠C++的重载规则。

14、

15、析构函数和构造函数的一些问题:

注意一点:析构函数是在类对象释放时候才被调用的,对于其他的指针可以用delete删除。

析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

以C++语言为例:[1]  析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显示的析构函数。

C++语言析构函数格式

编辑
C++当中的析构函数格式如下:
1
2
3
4
5
6
7
8
9
class <类名>
{
     public:
       ~<类名>();
};
<类名>::~<类名>()
{
    //函数体
};
如以下定义是合法的:
1
2
3
4
5
6
7
8
9
class T
{
   public:
    ~T();
};
    T::~T()
{
    //函数体
};
当程序中没有析构函数时,系统会自动生成以下析构函数:
<类名>::~<类名>(){},即不执行任何操作。

 

推荐阅读