C++部分——C++面向对象(2)

本文探讨了C++中初始化列表与赋值的区别,重点分析了必须使用初始化列表的情况,如const和引用成员变量的初始化及基类构造。此外,还讨论了静态成员的正确使用方法、构造函数调用顺序、空类默认成员函数、构造与析构函数重载等关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.有哪几种情况只能用intialization list,而不能用assignment

(初始化列表和赋值的区别)
无论是在构造函数初始化列表中初始化成员,还是在构造函数体中对它们赋值,最终结果都是相同的。不同之处在于,使用构造函数初始化列表初始化数据成员,没有定义初始化列表的构造函数在构造函数体中对数据成员赋值。
对于const和reference类型成员变量,它们只能够被初始化而不能做赋值操作,因此只能用初始化列表。
还有一种情况就是,类的构造函数需要调用其基类的构造函数的时候。如下代码:

#include<iostream>
using namespace std;

class A  //A是父类
{
private:
    int a;//private成员
public:
    A(){}
    A(int x):a(x){}//带参数的构造函数对a进行初始化
    void printA()//打印a的值
    {
        cout<<"a="<<a<<endl;
    }
};

public B:public A//B是子类
{
private:
    int b;
public:
    B(int x,int y):A(x)//需要初始化b以及父类的a
    {
    //a=x;//a为private,无法再子类中被访问,编译错误
    //A(x);//调用方式错误,编译错误
    b=y;
    }
    void printB()//打印b的值
    {
        cout<<"b="<<b<<endl;
    }
};
int main()
{
    B b(2,3);

    b.printA();//调用子类的printA()
    b.printB();//调用子类的printB()

    return 0;
}

从上面的程序可以看到,如果在子类的构造函数中需要初始化父类的private成员,直接对其赋值是不行的,(//a=x;//a为private,无法再子类中被访问,编译错误),只有调用父类的构造函数才能完成对它的初始化。但是在函数体内调用父类的构造函数也是不合法的(//A(x);//调用方式错误,编译错误),只有采取在初始化列表调用构造函数的方式。

综述:当类中含有const,reference成员变量和基类的构造函数时都需要初始化列表。

2.静态成员的错误使用

下列的代码有问题,找出来并说明原因。
(静态成员与非静态成员的理解)

#include<iostream>
using namespace std;
class test
{
public:
    static int i;
    int j;
    test(int a):i(1),j(a){}
    void func1();
    static void func2();
};
void test::func1(){cout<<i<<","<<j<<endl;}
void test::func2(){cout<<i<<","<<j<<endl;}
int main()
{
    test t(2);
    t.func1();
    t.func2();
    return 0;
}

这个程序有两个错误:
1.代码test(int a):i(1),j(a){},不能初始化i。为了与非静态成员变量相区别,i不能在类内部被初始化。可以把i放在类定义外面初始化。
2.void test::func2(){cout<

#include<iostream>
using namespace std;
class test
{
public:
    static int i;
    int j;
    test(int a):j(a){}
    void func1();
    static void func2();
};
int test::i=1;
void test::func1(){cout<<i<<","<<j<<endl;}
void test::func2(){cout<<i<</*","<<j<<*/endl;}
int main()
{
    test t(2);
    t.func1();
    t.func2();
    return 0;
}

3.对静态数据成员的正确描述

(考查对静态数据成员的理解和使用)
下列对静态数据成员的描述中,正确的是
A.静态数据成员可以在类体内进行初始化
B.静态数据成员不可以被类的对象调用
C.静态数据成员不能受private控制符的作用
D.静态数据成员可以直接用类名调用

正确的是CD.静态数据成员必须在类外进行初始化,以示与普通数据成员的区别。

4.main函数在执行之前还会执行什么代码?

(对构造函数调用期的理解)

#include<iostream>
using namespace std;

class Test
{
public :
        Test()//构造函数
        {
            cout<<"constructor of Test"<<endl;
        }
};
Test a;//全局变量
int main()
{
        cout<<"main() start"<<endl;
        Test b;//局部变量
        return 0;
}

这里写图片描述
显然,这里的执行顺序为:首先进行全局对象a的构造,然后进入main函数中,再进行局部对象b的构造。

所以:全局对象的构造函数会在main函数之前执行。

5.C++中的空类默认会产生哪些类成员函数

(编译器对C++类的默认处理)
对于一个C++的空类,比如Empty:

class Empty
{
};

虽然Empty类定义中没有任何成员,但为了进行一些默认的操作,编译器会加入以下一些成员函数,这些成员函数使得类的对象拥有一些通用的功能。

  • 默认构造函数和复制构造函数。它们被用于类的对象的构造过程。
  • 析构函数。它被用于类的对象的析构过程。
  • 赋值函数。它被用于同类的对象间的赋值过程。
  • 取值运算。当对类的对象进行取地址(&)时,此函数被调用。

    即虽然程序员没有定义类的任何成员,但是编译器也会插入一些函数,完整的Empty类定义如下。

class Empty
{
public:
Empty();//缺省构造函数
Empty(const Empty&);//复制构造函数
~Empty();//析构函数
Empty& operator=(const Empty&);//赋值运算符
Empty* operator&();//取址运算符
const Empty* operator&() const;//取址运算符 const
}

因此,C++的空类中,默认会产生默认构造函数,复制构造函数,析构函数,赋值函数以及取址运算。

6.构造函数和析构函数是否可以被重载

(对构造函数和析构函数的理解)
构造函数可以被重载,因为构造函数可以有多个,且可以带参数。
析构函数不可以被重载,因为析构函数只能有一个,且不能带参数。

7.关于重载构造函数的调用

(重载构造函数的调用)

class Test
{
public:
    Test(){}
    Test(char *Name,int len=0){}
    Test(char *Name){}
};
int main()
{
    Test obj("hello");
    return 0;
}

下面对程序执行结果的描述中,正确的是()B
A.将会产生运行时错误
B.将会产生编译错误
C.将会执行成功
D.以上说法都不正确

Test定义了两个构造函数。当编译到代码的Test obj(“hello”)时,由于构造函数的模糊语意,编译器无法决定调用哪一个构造函数,因此会产生编译错误。
另外,如果把上面那一行注释掉,编译器将不会产生错误。因为C++编译器认为潜在的二义性不是一种错误。

8.构造函数的使用
以下代码中语句输出0吗?为什么?

#include<iostream>
using namespace std;

struct CLS
{
    int m_i;
    CLS(int i):m_i(i){}
    CLS()
    {
        CLS(0);
    }
};
int main()
{
    CLS obj;
    cout<<obj.m_i<<endl;
    return 0;
}

在代码第10行,即CLS(0),不带参数的构造函数直接调用了带参数的构造函数。这种调用往往被很多人误解,以为可以通过构造函数的重载和相互调用实现一些类似默认参数的功能,其实是不行的,而且往往会有副作用。下面加几条打印地址的语句到原来的程序中。

#include<iostream>
using namespace std;

struct CLS
{
    int m_i;
    CLS(int i):m_i(i)
    {
        cout<<"CLS():this="<<this<<endl;
    }
    CLS()
    {
        CLS(0);
        cout<<"CLS(int):this="<<this<<endl;
    }
};
int main()
{
    CLS obj;
    cout<<"&obj="<<&obj<<endl;
    cout<<obj.m_i<<endl;
    return 0;
}

程序执行结果如下:
这里写图片描述
可以看到,在带参数的构造函数里打印出来的对象地址和对象obj的地址不一样。实际上, CLS(0);代码13行的调用知识在栈上生成了一个临时对象,对自己本身毫无影响。还可以发现,构造函数的互相调用引起的结果不是死循环,而是栈溢出。

所以,输出的结果不为0,是个随机数。
原因是构造函数内调用构造函数只是在栈上生成了一个临时对象,对于自己本身毫无影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值