C++基础知识点总结四

来源:互联网 发布:linux 服务器 反应慢 编辑:程序博客网 时间:2024/06/08 12:40

(对象)在传入参数,及返回参数时,都会调用对象的拷贝构造函数;相应的,返回时都会析构两次。

即,多调用两次构造和析构函数。
FunTestTwo(指针)和FunTestThree(引用):都没有调用拷贝构造及析构函数。

结论:

对于大型程序,如果传入的对象TestSimple很大,多调用两次拷贝构造及析构函数,需要消耗大量内存资源。


函数传参,传入指针和传入引用的区别:

指针传递参数本质上是值传递的方式,它所传递的是一个地址值,成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值成为了实参的一个副本。

而在引用传递过程中,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

用一个指针指向数组a,那么指针和a的都指向相同的地址,内容是第一个元素的内容,

用一个指针指向对象a,那么指针和对象都指向相同的地址,内容是对象里面所有元素

这里测试了一下:

class A{ public:int a=4;double b = 5;char c[10] = "sdfsd"; };int main(){char s[] = "123456789";char *p = s;A *pp = new A();cout << sizeof(*p) << endl;cout << sizeof(*pp)<< endl;getchar();return 0;}
输出为1和32,可见指针指向数组,内容是第一个元素,指针指向对象,内容是所有元素

float 为8字节,double为16字节

sizeof总结:

1,只要是指针,sizeof一定是4字节,sizeof(数组)=如果分配了数组大小乘以数据类型,就为该大小,没分配大小就为数组的.length+‘\0’

char ss2[]="0123456789"           sizeOf(ss2)    //  为11

char ss2[100]="0123456789"       //sizeof为100

int ss4[100]           //为400

int ss[]={12,23,45}   //为12

char q1[]="a\n"    //为3,因为\n是一位因此为3

char s[] = "abcde";   那么strlen(s)为5

一个笔试题中写了父类和子类,有同名函数,父类是虚的,那么sizeof(父类)=4,sizeof(子类)=4,因为每个类都有一个指针所以为4,但是想想这个答案感觉怪怪的

如果一个类为空,sizeof(类)=1,但是如果涉及到虚继承(虚表,虚指针),sizeof(类)=4

c++中值传递在函数中交换2个数的值其实是副本机制,不会改变实参的,但是传参传指针和引用都可以改变。

在c++中两个指针相减等于地址相减除以该指针类型的大小


例题1:

int v[2][10]={{1,2,3,4,5,6,7,8,9,10},{11,12,13,14,15,16,17,18,19,20}}

int (*a)[10]=v;

**a是什么,**(a+1)是什么,*(*a+1)是什么,*(a[0]+1)是什么,*(a[1])是什么

初看这个题有点懵逼,不过没关系,我们慢慢来,在一维数组中int a[]中a为第一个元素的地址,a+n表示第一个元素的地址+n*元素的类型大小(元素的长度)=a[n],

*a+n表示第一个元素的大小加上n,那好。我们这样推二维数组,用一个二维指针指向一个二维数组,那么这个二维指针a表示什么,表示的是这个二维数组的地址,那么a[0]表示二维数组第一行的起始地址,a[1]表示二维数组第二行的起始地址,a+1表示a的起始地址加上a[0]整片的长度。*(a+1)等价于a[1]表示a的第二段起始地址

这里有个很注意的是:*(a+1)和*a+1和a+1的区别,

a+1表示a+1表示a的起始地址加上a[0]整片的长度

*(a+1)也即a[1], 表示a[0]的地址,加上a中元素的长度,也即a[0]的起始地址+a[0]整个一维数组的长度,因为a[0]有十个数,每个数为int,所以为a[0]的起始地址+4*10

*a+1表示,a[0]的地址,加上a[0]这个一维数组中一个元素的大小,也即a[0][1],这里*a+1和a[0]+1是等价的。

还要非常注意的是&a和a的区别,&a+1表示a起始地址加上a整片长度,a+1表示a的起始地址+a中元素的长度

因此最后的答案是:

**a为1,**(a+1)为*a[1]为11,*(*a+1)为2,*(a[0]+1)为2,*(a[1])为11


例题2:一个指向整形数组的指针定义为:

A int(*ptr)[]   B int *ptr[]

我们来看,A表示一个数组指针,指向这个数组,B表示一个数组,每个元素为int指针,果断B哇。

指针数组:如果一开始比较懵逼,先想想数组指针是什么,是一个指针指向数组,那么指针数组是一个数组,每个元素为指针类型

数组指针:一个指针指向整个数组


例题3 

int a[]={1,2,3,4,5};

int *ptr=(int*) (&a+1);

printf("%d %d",*(a+1),*(ptr-1))

最终答案为2 5


例题四

c++中class和struct有什么区别?

class里面默认是private,c++中struct里面默认为public,其他没区别

存在struct的意义是让C程序员有个归属感,兼容以前C的项目


C#中class和struct有什么区别?

struct是值类型,创建一个struct类型的实例被分配在栈上。class是引用类型,创建一个class类型实例被分配在托管堆上

例题五, 如果把静态成员数据设为私有,如何访问?

答:通过公有静态成员函数访问,(单例模式就是这样)


例题六, 为什么父类的析构函数设为虚函数是必要的?

答:因为虚函数的主要用处在于多态,如果不设为虚函数,那么创建子类先调用父类构造函数在调用子类构造函数,撤销子类时先调用子类的析构在调用父类析构,这没问题,但是在多态下,父类 父类名=new 子类();创建了子类,那么会先调用父类的构造函数在调用子类的构造函数,但是当撤销时,是撤销父类名,如果父类析构不是虚的,那么直接调用父类的析构,后果是,如果之前调用子类构造函数在堆中分配内存,但是没有调用子类析构,那么会造成内存泄漏;


例题七。为什么析构函数可以为虚的,构造函数不能?

答:虚函数主要用在多态下,形参用父类接收,但是不知道接收的是父类还是子类,因此父类用虚函数,父类名.函数来通过多态调用方法,但是构造函数是在new的时候调用的。你new的时候一定要清楚new的是子类还是父类。所以构造函数不能为虚


例题八 编写类String的构造函数,析构函数,和赋值函数

String类的构造函数,形参传的是char*,我们要将传进来的字符串保存下来。

String::String(const char* str){

        //先判断传入的形参有没有异常情况

      if(str==null){

     m_data=new char[1];

     *m_data='\0';

     }else{    //否则正常情况

     int length=strlen(str);

     m_data=new char[length+1];

     strcpy(m_data, str);      //必须要通过该函数深拷贝,否则拿m_data=str就浅拷贝了,这个函数自动把最后一位设为'\0',如果是手动复制,记得最后一位还要手动添加到

     }}

    String类的析构函数:

   String::~String(){

    delete []m_data;   //由于在类中保存了一个char数组,所以要释放

     }

      String类的拷贝构造函数:

    String::String(const String &other){

     m_data=new char[strlen(other.m_data)+1];

     strcpy(m_data, other.m_data);

}

      String类的赋值函数 :这里赋值的意思是这样的,String s1=“hello”;  String s2;     s1=s2;

     String & String::operate=(const String &other){

     //先判断参数传入异常 ,如果传入的对象是自身

    if(this==&other){      //因为this是当前对象的指针,是个地址所以不能是this==other

     return *this;

      }

      delete [] m_data;  //我原本想的直接深拷贝,但是还有原m_data存在的可能,所以先释放

      m_data=new char(strlen(other.m_data)+1);

     strcpy(m_data, other.m_data);

     return *this;

      }


关于虚函数有几点需要注意:

1,如果b继承a,都有同名虚函数,多态下,a.函数执行的时候发现a的函数为虚函数,自动执行b中的同名虚函数

2,如果b继承a,都有同名虚函数,如果A a=new A();因为new的是A所以a.函数执行的是a的虚函数

3,如果b继承a,都有同名函数,b的是虚函数,a不是。多态下,a.函数,执行时发现a的函数不是虚函数,自动执行a的函数,不会执行b的

4,注意new的是谁,new谁表示最高执行到那个类的同名函数


公有继承,私有继承和保护继承

对于一个类的私有成员不管什么继承都获取不到

公有继承:(class Cat: public Animal),比如animal有eat函数(public),cat公有继承animal,cat没有写eat函数,但是cat.eat()仍然能调用animal.eat()的函数.对于子类内部,定义子类内部的时候可以调用父类的public和protect类型的所有,但是如果把子类当做对象用的时候,cat.使用对象.的方式调用时,只能调用父类的public类型,protect获取不到。

私有继承(class Cat:private Animal),子类私有继承父类,写子类内部的时候可以调用父类的公有和保护类型的所有,而且基类的公有成员和保护成员作为子类的私有成员。这句话的意思是,子类这一代能获取到父类的公有和保护,然后就绝种了,其子类的子类都获取不到了。当子类作为对象使用的时候,使用对象.的方式调用,父类的所有类型都获取不到

保护继承(class cat:protectd Animal):和私有继承一样,区别是在cat的内部,能获取到父类的public和protect成员,并且都作为子类的保护成员。不能被子类的子类访问。同理,作为子类对象.的形式调用时,获取不到父类的任何类型

//公有继承                      对象访问    成员访问public    -->  public              Y         Yprotected -->  protected           N         Yprivate   -->  private             N         N//保护继承                      对象访问    成员访问public    -->  protected           N         Yprotected -->  protected           N         Yprivate   -->  private             N         N//私有继承                      对象访问    成员访问public    -->  private             N         Yprotected -->  private             N         Yprivate   -->  private             N         N

      既然私有继承和保护继承对象不能通过子类对象.的方式访问父类的public方法,那么怎么办呢?和单例模式相同,我们在私有或保护的子类内部写一个方法,该方法调用父类的public的方法,这样在外部,子类对象.函数就可以访问啦。


虚函数表每个对象都有一个指针,指向虚表,虚表中存放了虚函数的地址,

如果一个类中有虚函数,不管多少个,sizeof(类)都为4,它是一个指向虚表的指针,指针为4字节,如果该类没有虚函数,是普通函数,那么sizeof为1字节,按空类处理。

如果父类有虚函数,子类不管有没有虚函数,不管函数是不是同名,sizeof(子类)都为4字节(前提是父类子类只有函数没有成员变量,这里说的是子类也必须有虚函数表的指针)

虚函数表的原理:

class A {public:    virtual void vfunc1();    virtual void vfunc2();    void func1();    void func2();private:    int m_data1, m_data2;};

我们看到类a中有虚函数,那么类a中保存了一个指向虚函数表的指针,在c++中叫做vptr,还保存了两个int 的成员变量,在虚函数表中是一个数组,每个元素是个指针,指向每个虚函数的地址。

对于继承,虚函数表时这样的原理:

class A {public:    virtual void vfunc1();    virtual void vfunc2();    void func1();    void func2();private:    int m_data1, m_data2;};class B : public A {public:    virtual void vfunc1();    void func1();private:    int m_data3;};class C: public B {public:    virtual void vfunc2();    void func2();private:    int m_data1, m_data4;};

我们可以看到因为每个类都有虚函数表,因为父类有虚函数,并且是继承关系,所以子类都有虚函数表,因此每个类都保存一个指向各自虚函数表的指针,在每个虚函数表中也即虚函数数组中,是按照顺序的,第一个指针指向调用哪个类的第一个虚函数,第二个指针指向调用哪个类的第二个虚函数

    B bObject;    A *p = & bObject;    p->vfunc1();
如果B继承A,如上的代码,首先p是个指针,所以调用函数使用p->而不是p.的方式,其次,p是指针,指向的是b的地址,那么p->vfunc1()是运行的b的虚表的函数,找到对应的函数即可

虚函数表补充的部分(做聚乐科技的笔试题的一道题)

class A {    public: void fun() { cout << "A" << endl; }};class B : public A {    public:virtual  void fun() { cout << "B" << endl; }};class C : public B {public:void fun() { cout << "C" << endl; }};class D : public C {public:virtual void fun() { cout << "D" << endl; }virtual  void funD() { cout << "D_test" << endl; }};class E : public D {public:virtual  void fun() { cout << "E" << endl; }virtual void funE() { cout << "E_test" << endl; }};class E1 : public D {public:virtual  void fun() { cout << "E1" << endl; }virtual void funE1() { cout << "E1_test" << endl; }};

答案A,C,D,D,D_Test,E_Test,D_Test,3,3


这里重点讲下

B* p4=(E*)new D;             这里把new出的D强转成了E*,记住new的谁就是谁,那么还是D

E1* p6=(E1*) new E;   

p6->funE1();

注意E和E1都继承D, new的是E,那么开辟的就是E的空间,E的vptr指向2个虚函数fun和funE,但是调用的是E1中的funE1函数,因为,funE1在E1中是虚函数表中的第二个虚函数,所以执行E中虚函数表的第二个函数,输出为E_Test


原创粉丝点击