2.1 类

  • 一、要点归纳
    • 1.类的定义
    • 2.类的访问权限
    • 3.内联函数
      • 3.1 什么是内联函数
      • 3.2 内联函数的使用
      • 3.3 内联函数和宏定义的区别
    • 4.构造函数
      • 4.1 构造函数的性质
      • 4.2 自动调用构造函数
      • 4.3 显示调用构造函数
      • 4.4 一种特殊的调用构造函数方式
    • 5.拷贝构造函数
    • 6.析构函数
      • 6.1 析构函数的性质
      • 6.2 析构函数的调用
    • 7. 类对象
      • 7.1 对象的定义格式
      • 7.2 类对象的内存存储方式
    • 8.空类
    • 9.类与结构体类型的区别
  • 二、面试真题解析
    • 1.面试题1
    • 2.面试题2
    • 3.面试题3
    • 4.面试题4
    • 5.面试题5
    • 6.面试题6

一、要点归纳

1.类的定义

  类是一种用户自定义的数据类型,用于组织数据和数据操作。类是面向对象程序设计的基础。定义类的一般格式如下:

//类声明
class 类名
{private:私有数据成员和成员函数;protected:保护数据成员和成员函数;public:公有数据成员和成员函数;
}; //必须以;结尾
//类实现
静态数据成员初始化;
各个成员函数的定义;

  前面的class{……}称为类声明,类声明告诉编译器有一个指定名称的类,它具有那些数据成员、那些成员函数,并没有为数据成员和数据函数分配内存空间。
  后面的部分称为类实现,当遇到类实现是C++编译器会为静态数据成员分配内存空间并初始化,但不会为其他成员数据成员分配内存空间,同时会为成员函数分配代码空间,这样每个成员函数都有一个存储其代码的地址。在进入链接阶段时将通过对象调用成员函数的地方用对应函数的地址替代,并传递当前对象的指针和相关参数。例如有以下程序:

//类声明
class A
{int n;
public:A(int m) { n=m; };void disp();
}; void main()
{A a(2);//a.disp();
}

  上述程序中仅仅声明disp()函数而并没有实现它,所以没有为,disp()函数分配内存空间,即类A的成员函数disp()没有对应的有效地址。但程序执行不会有任何问题,因为程序中没有出现调用它的地方。如果在main函数中定义a对象之后调用disp()成员函数你,即a.disp(),就会出现链接错误。这是因为在链接时替代a.disp()语句时找不到disp()成员函数的地址。
通常将类声明和类实现合起来称为类定义。

2.类的访问权限

  类成员有数据成员和成员函数,在类成员声明中包含public、private和protected关键字,他们是成员访问限定符,用于设置类成员的访问权限。

  • private:声明私有成员。私有数据成员只允许在类中访问,私有成员函数只允许被类中的其他成员函数调用。在类外不允许访问私有数据成员,也不允许调用私有成员函数。
  • public:声明共有成员。共有数据成员允许在类中或类外访问,公有成员函数允许在类中或者类外调用。
  • protected:声明保护成员。保护数据成员只允许在类中或者其子类中访问,保护成员函数允许在类中或其子类中调用。在类外不能访问该类的保护数据成员,也不能调用该类的保护成员函数。

3.内联函数

3.1 什么是内联函数

  类的成员函数用于实现某种操作,成员函数的定义体可以放在类声明中,也可以放在类声明体外。在类声明体中实现的函数称为内联函数,在类声明体外实现的函数可以通过在函数声明和定义时加上inline来表示改函数是内联函数,否则不是内联函数。实际上,普通函数(非类的成员函数)也可以加上inline变成内联函数。
  C++编译器在遇到调用内联函数的地方会用函数体中的代码来替换函数的调用,好处是节省函数调用带来的参数传递、栈空间的进栈与出栈等开销,从而提高执行速度,但付出的代价是增加了代码长度。例如一下定义了一个内联函数abs():

inline int abs(int x)
{if(x<0) return -x;else return x;
}

  在调用abs()的地方编译器会自动调用函数体代替函数调用。例如有了上述定义的内联函数后,加入main()函数如下:

void main()
{int m,m1=2,n,n1=-10;m=abs(m1);n=abs(n1);std::cout<<"m="<<m<<",n="<<n<<std::endl;
}

编译器会自动转换成如下代码:

void main()
{int m,m1=2,n,n1=-10;if(m1<0) m=-m1;else m=m1;if(n1<0) n=-n1;else n=n1;std::cout<<"m="<<m<<",n="<<n<<std::endl;
}

3.2 内联函数的使用

3.3 内联函数和宏定义的区别

  由于内联函数一般实现较小的功能,所以可以采用宏定义来达到同样的目的,但两者是有区别的:

  1. 内联函数在执行时可调式,而宏定义不可以;
  2. 编译器会对内联函数的参数类型做安全检查活自动类型转换,而宏定义不会;
  3. 内联函数可以访问类的成员变量,而宏定义不能;
  4. 在类中声明同时定义的成员函数自动转化为内联函数;

4.构造函数

4.1 构造函数的性质

  构造函数是一种特殊的成员函数,主要用于对象数据成员的初始化工作。构造函数具有如下性质:

  1. 构造函数的名称与类名相同;
  2. 构造函数没有任何函数类型,也不属于void函数;
  3. 一个类除了不带参数的默认构造函数之外,还可以设计一个或多个带参数的重载构造函数;
  4. 如果一个类中没有定义任何构造函数,编译器会自动生成一个不带参数的默认构造函数。如果一个类中声明有任何带参数的构造函数,编译器不会自动生成不带参数的默认构造函数。

4.2 自动调用构造函数

  在C++中内置的数据类型都可以看成类,在定义变量时除了可以使用“=”运算符赋初值之外,还可以像定义类对象一样调用其构造函数给变量赋初值。例如如下语句都是正确的:

int n(10);//等价于int n=10;
char s[]("asd");//等价于char s[]="asd";

因此在构造函数中可以通过成员初始化列表对数据成员初始化,例如:

class A
{private:int n,m;
public:A (int m){n=m;}A (int x,int y):m(x),n(y)//含成员初始化列表的重载构造函数{//构造函数的函数体为空}void disp(){std::cout<<m<<" "<<n<<std::endl;}
};
void main()
{A a(1,2);a.disp();return 0;
}

当构造函数中带有成员初始化列表时首先执行初始化列表,再执行函数体;

4.3 显示调用构造函数

  类的构造函数可以显示调用,实际上在前面程序的main()函数中A a(1,2);语句可以写为A a=A(1,2),也就是说将A a=A(1,2)转化为A a(1,2)语句。例如有以下程序:

class A
{private:int n,m;
public:A() {m=0;n=0;std::cout<<"默认构造函数\n"<<std::endl;}A(int x,int y){m=x;n=y;std::cout<<"重载构造函数"<<std::endl;}~A(){std::cout<<"析构函数"<<std::endl;}A& operator=(const A& b){m=b.m; n=b.n;std::cout<<"operator=";return *this;}A set(int x,int y){return A(x,y);}//自定义成员函数void disp(){std::cout<<m<<" "<<n<<std::endl;}
};
void main()
{A a=A(1,2),b;b=a.set(3,4);a.disp();b.disp();
}

程序输出如下:

重载构造函数
默认构造函数重载构造函数
operator=析构函数
1 2
3 4
析构函数
析构函数

执行main()函数,对于A a=A(1,2),b语句,调用重载构造函数创建对象a,调用默认构造函数创建对象b。对于b=a.set(3,4)语句,执行return A(x,y)语句,其中A(x,y)显示调用ID在构造函数创建一个临时对象。调用operator=将其数据成员赋给对象b,并销毁这个临时对象。语句a.disp()输出对象a的数据成员,语句b.disp()输出对象b的数据成员,并销毁这个临时对象,最后依次销毁对象b和a。

4.4 一种特殊的调用构造函数方式

  有一种特殊的带参数构造函数的情况,如果一个类A中只有一个数据成员,并设计有带参数的重载构造函数,则可以通过A a=1的方式创建对象a,例如:

class B
{int n;
public:B(){n=0;std::cout<<"默认构造函数"<<std::endl;}B(int m){n=m;std::cout<<"重载构造函数"<<std::endl;}void disp(){std::cout<<n<<std::endl;}
};
void main()
{B b=1;b.disp();
}

在执行main()中的B b=1语句时创建类B对象b,并将其数据成员初始化为1。该语句等同于B b(1)。如果类B中有两个或者多个数据成员,不能采用这种方式创建对象。

5.拷贝构造函数

拷贝构造函数是另外一种特殊的构造函数,具有一般构造函数的所有特性,主要用于通过一个已存在的类对象创建一个新的类对象时实现对象之间的数据成员的复制操作。拷贝构造函数的一般格式如下:

类名::拷贝构造函数(const 类名& 引用名){……}

引用名参数采用const修饰,表示不能修改对应的引用参数。每个类都必须有一个拷贝构造函数,如果没有声明,编译系统自动生成一个具有上述形式的默认拷贝构造函数,以实现数据成员的简单复制。
在以下三种情况拷贝构造函数会自动被调用:

  1. 用类的一个已知对象(已创建)去初始化该类的另一个对象(创建的新对象)时;
  2. 函数的形参是传值类对象(非引用对象),调用函数进行形参和实参的结合时;
  3. 函数的返回值是类对象(非引用对象),函数执行完返回给调用着时;
    例如有以下程序:

class C
{int x,y;
public:C(){std::cout<<"默认构造函数"<<std::endl;}C(int x1,int y1){std::cout<<"重载构造函数"<<std::endl;x=x1,y=y1;}C(const C& obj){std::cout<<"拷贝构造函数"<<std::endl;x=obj.x,y=obj.y;}C add(const C obj){std::cout<<"add"<<std::endl;return C(obj.x+1,obj.y+1);}void disp(){std::cout<<"("<<x<<","<<y<<")"<<std::endl;}
};void main()
{C a(1,2),b(a),c;std::cout<<"a:";a.disp();std::cout<<"b:";b.disp();c=a.add(b);std::cout<<"c:";c.disp();
}

输出如下:

重载构造函数//创建对象a
拷贝构造函数//创建对象b
默认构造函数//创建对象c
a:(1,2)
b:(1,2)
拷贝构造函数//调用add时实参b和形参obj的结合
add
重载构造函数//执行add函数的C(obj.x+1,obj.y+1)语句创建临时对象1
//有的编译器会再在执行完add时候运行拷贝构造函数时生成一个临时对象2,有的编译器进行过优化,就不额外产生这个临时对象2了。
c:(2,3)

6.析构函数

6.1 析构函数的性质

  和构造函数一样,析构函数也是类的特殊成员函数。析构函数具有如下性质:

  1. 析构函数在类对象销毁时自动执行,用于对象内存的清理工作;
  2. 一个类只能有一个析构函数,而且析构函数没有参数;
  3. 析构函数的名称是~加上类的名称;
  4. 析构函数也没有任何函数类型,不能像普通函数成员那样显示调用;
  5. 如果一个类没有声明析构函数,编译器会自动生成一个函数体为空的默认析构函数;

6.2 析构函数的调用

在程序执行的过程中,当遇到对象的生存期结束时系统会自动调用析构函数,然后回收为对象分配的存储空间。例如有以下程序:

class D
{int x,y;
public:D(){std::cout<<"默认构造函数"<<std::endl;}D(int x1,int y1){std::cout<<"重载构造函数"<<std::endl;x=x1;y=y1;}D(const D& obj){std::cout<<"拷贝构造函数"<<std::endl;x=obj.x,y=obj.y;}~D(){std::cout<<"析构函数"<<std::endl;}D add(const D&obj){std::cout<<"add";return D(obj.x+1,obj.y+1);}void disp(){std::cout << "(" << x << "," << y << ")" << std::endl;}};
void main()
{D a(1,2),b;b=a.add(a);std::cout<<"b: ";b.disp();
}

输出结果如下:

重载构造函数//创建对象a
默认构造函数//创建对象b
add重载构造函数//执行add函数的D(obj.x+1,obj.y+1)语句创建一个临时对象
析构函数//销毁临时对象
b: (2,3)
析构函数//销毁对象b
析构函数//销毁对象a

7. 类对象

7.1 对象的定义格式

  一旦定义了一个类,就可以用它定义类对象。在C++中类对象也称为类变量或者类实例。定义类对象的格式如下:

类名 对象名表;

例如在类MyClass定义好了之后,以下语句用于定义他的对象:

MyClass obj1,obj2,*pobj,obj[10];

其中obj1和obj2是一般对象名,pobj是指向对象的指针,obj是对象数组的数组名,它有10个元素,每个元素都是一个对象。

7.2 类对象的内存存储方式

  可以定义类的一个或者多个对象每个对象单独存储,每个对象都有唯一的地址。C++只为每个对象的所有非静态数据成员分配内存空间,并按照定义的先后顺序依次存放,而类的成员函数存放在代码区,为该类的所有对象所共享,不占对象的内存空间。一个对象占用的存储空间大小可以使用sizeof运算符求出。
类对象也可像普通变量一样具有存储类别,根据对象的存储类别在相应内存区中分配空间,例如有以下程序:

class Student
{public:int no;char name[10];void setvalue(int xh,char xm[]);void display();
};
void Student::setvalue(int xh, char xm[])
{no = xh;strcpy(name, xm);
}
void Student::display()
{std::cout<<"学号:"<<no<<" 姓名:"<<name<<std::endl;
}
void test08()
{Student s1,s2;s1.setvalue(101,"张三");s2.setvalue(108,"李四");s1.display();s2.display();std::cout << "s1占用空间:" << sizeof(s1) << " s2占用空间:" << sizeof(s2) << std::endl;std::cout << "s1地址:" << &s1 << " s1.no地址:" << &s1.no << " s1.name地址:" << &s1.name << std::endl;std::cout << "s2地址:" << &s2 << " s2.no地址:" << &s2.no << " s2.name地址:" << &s2.name << std::endl;printf("display地址:%p\n", &Student::display);
}
test08
学号:101 姓名:张三
学号:108 姓名:李四
s1占用空间:16 s2占用空间:16
s1地址:0x7fff94d97170 s1.no地址:0x7fff94d97170 s1.name地址:0x7fff94d97174
s2地址:0x7fff94d97180 s2.no地址:0x7fff94d97180 s2.name地址:0x7fff94d97184
display地址:0x55fadb2af76a

  上述程序中定义了Student类,在main函数中定义了两个局部变量s1和s2,他们是在栈空间中分配的,分别调用两个成员函数设置和输出其数据成员值,再通过sizeof运算符输出这两个对象占用的内存空间大小,最后输出相关地址。

在了解了对象的存储结构后,可以采用指定地址中值的方式访问对象的私有数据成员:

class E
{int x,y;
public:E(int i,int j){x=i,y=j;}
};
void test09()
{E e(2,5);std::cout << "e.x=" << *(int *)(&e) << std::endl;       // 输出e.x=2std::cout << "e.y=" << *((int *)(&e) + 1) << std::endl; // 输出e.y=5
}

8.空类

  C++的空类是指设计时不包含任何数据成员和数据函数的类,形如:

class A
{};

实际上,对于一个空类,系统会自动添加以下默认的成员函数:

  • 默认构造函数
  • 默认拷贝构造函数
  • 默认析构函数
  • 赋值=运算符
  • 取地址运算符
  • 取地址运算符const

对于空类A,sizeof(A)=1。这是实例化的原因。每个实例在内存中都应该有一个唯一的地址,为了达到这个目的,编译器会给每一个空类插入一个字节,这样空类在实例化后在内存中得到唯一的地址,所以空类所占的内存大小是1个字节。

9.类与结构体类型的区别

  结构体类型与类的唯一区别在于类成员的默认访问权限是私有的,而结构体类型成员的默认访问权限是公有的。

  通常,当只需要描述数据结构时使用结构体类型较好,当急需要描述数据,有需要描述对数据的处理过程时使用类较好。

二、面试真题解析

1.面试题1

【面试题】在头文件中进行类的声明,在对应的实现文件中进行类的定义有什么意义?
【答】这样可以提高编程效率。如果分开只需要编译一次生成对应的.obj文件,然后在应用该类的地方就不会被再次编译,从而大大提高了编译效率,另外使得程序结构更加清晰。

2.面试题2

【面试题】关于拷贝构造函数的说法正确的是(B)

  • A. 每个对象都存放一份拷贝构造函数
  • B.提供一个默认的拷贝构造函数用于对象间数据成员的简单拷贝
  • C.拷贝构造函数的参数既可以是引用型参数也可以是非引用型参数
  • D.以上都正确
    【答】B.拷贝构造函数和其他成员函数一样存放在类的公共区,拷贝构造函数的参数必须是引用型参数。

3.面试题3

【面试题】下面代码中a,b,c,d各自存储在什么区?

int a=0;
class someClass
{int b;static int c;
};
void main()
{int d=0;someClass *p=new someClass();
}

【答】a为初始化的外部变量,存放在静态数据区(全局变量区);对象指针p指向的实例存放在堆空间中,所以p->b存放在堆空间中;c为类的未初始化的静态数据成员,为类变量,存放在类公共区(BBS)中。d为main函数的局部变量,存放在栈空间中。

4.面试题4

【面试题】执行以下程序输出的结果是(AXBB)

class TestClass
{char x;
public:TestClass(){cout<<'A';}TestClass(char c){cout<<c;}~TestClass(){cout<<'B';}
};
void main()
{TestClass p1,*p2;p2=new TestClass('X');delete p2;
}

【答】执行TestClass p1,*p2语句调用默认构造函数创建对象p1。执行p2=new TestClass(‘X’)语句调用重载构造函数创建p2指向的实例。执行delete p2语句调用一次析构函数销毁p2指向的实例。最后调用一次析构函数销毁对象p1。

5.面试题5

【面试题】如果myclass类定义了拷贝构造函数和一个整形参数的构造函数,还重载了赋值运算符,那么执行myclass obj=100语句会(B)

  • A. 调用拷贝构造函数
  • B. 调用整形参数的构造函数
  • C. 调用赋值运算符
  • D. 引起编译错误

【答】myclass obj=100语句等价于myclass obj(100)语句,通过调用整型参数的构造函数来创建obj。

6.面试题6

【面试题】指出下面代码的错误之处:

class CSomething
{char *m_c;
public:CSomething(){m_c=new char}~CSomething(){delete m_c;m_c=NULL;}CSomething(const CSomething &other){*m_c=*ohter.m_c;}CSomething & operator=(const CSomething& a1){...}
};

【答】拷贝构造函数用于创建一个新对象,而上述拷贝构造函数中没有为新创建的对象分配m_c指向的空间。应该改为:

CSomething(const CSomething & other)
{m_c=new char;*m_c=*ohter.m_c;
}

【直击招聘C++】2.1 类相关推荐

  1. 直击招聘程序员面试笔试C语言深度解析,直击招聘 程序员面试笔试C++语言深度解析(直击招聘) pdf epub mobi txt 下载...

    直击招聘 程序员面试笔试C++语言深度解析(直击招聘) pdf epub mobi txt 下载 图书介绍 ☆☆☆☆☆ 李春葆,李筱驰 著 下载链接在页面底部 发表于2021-05-18 类似图书 点 ...

  2. 福建农村信用社计算机类C卷考什么,2015年福建省农村信用社公开招聘考试《计算机类》真题及详解...

    2015年福建省农村信用社公开招聘考试 <计算机类>真题 (总分:120.00,做题时间:90分钟) 一.判断题(总题数:10,分数:10.00) 1.不同的进程可以包含同一个程序,同一个 ...

  3. 电网招聘考试其他工学类难吗?考什么?怎么备考?

    Hello,大家好,我是莹子,本期推文主要介绍国家电网招聘考试中其他工学类的备考. 我将分为以下五个方面进行介绍: 国家电网招聘考试报名要求 什么专业能报考其他工学类 其他工学类的招聘名额 其他工学类 ...

  4. 中公电网计算机类题库讲练版百度云,2021电网二批招聘考试题库:计算机类练习题(5)...

    2021电网二批招聘笔试各专业的考试考什么?电网二批各专业笔试题型一样吗?国家电力电网好考吗?山西国企招聘网给大家带来2021国家电力电网二批招聘各专业笔试备考模拟练习题,希望能提供一定的帮助. 41 ...

  5. 农业银行招聘考试题目计算机类,农业银行招聘考试计算机专项练习(二).doc...

    农业银行招聘考试计算机专项练习(二) 计算机应用选择题练习 考核知识点试题数量计算机基础2-3Windows4-5Word4-5Excel4-5PowerPoint2-3网络与Internet2-3 ...

  6. 华为社招机考考什么_牛客网-华为-2020届校园招聘上机考试-软件类机考-3

    题目描述: Apache Hadoop YARN是一种新的Hadoop资源管理器,主要部件为resource manager和node manager.resource manager使用有限状态机维 ...

  7. 小米2017校园招聘(服务端开发类)

    小米依然不刷简历,对非985,211的高校一视同仁,不像某易,哈哈. 到目前为止,做过的网上笔试也有十几家了,小米的题量出的还是比较合适的,10道选择题,3道编程题,还有两道不计分的附加题. 觉得小米 ...

  8. 腾讯2014校园招聘软件后台开发类笔试题

    转http://www.itmian4.com/forum.php?mod=viewthread&tid=3572

  9. 华为社招机考考什么_牛客网-华为-2020届校园招聘上机考试-软件类机考-2

    题目描述: 输入一个字符串(不含空格), 请寻找输入中包含所有蛇形字符串. 蛇形字符串定义: 1.蛇形字符串由连续字符对组成,其特点如下: 1.1 字符对定义:字符对由同一字母的大写和小写组成(前大后 ...

最新文章

  1. linux中配置phpcms v9 中的sphinx
  2. 2015 UESTC 搜索专题B题 邱老师降临小行星 记忆化搜索
  3. 北师大名教授通过趣味数学与幽默教你学数学思维
  4. Windows ESXI 5.5 升級到 VCSA 6.5
  5. 用c语言编程解决数学实际问题,运用C语言解决爱因斯坦的数学题
  6. ROS机器人系列竞赛之地下挑战赛 The DARPA Subterranean (SubT) Challenge Competition
  7. 如何运营好一个微信公众号?
  8. html标签加载状态,如何让html页面数据没有加载完前显示loading加载中
  9. 用Python爬取斗鱼各区的主播信息,并制作热度排行榜
  10. 常见的的水生植物图像
  11. iOS 图片滚动播放
  12. java ftp 下载 0k_Ftp下载文件大小为0 KB
  13. linux双系统如何选择顺序,Ubuntu和Windows双系统选择开机顺序
  14. IDEA 2021 没有Allow parallel run
  15. html类选择器和id选择器,类和ID选择器的区别
  16. 【YOLOv5】手把手教你使用LabVIEW ONNX Runtime部署 TensorRT加速,实现YOLOv5实时物体识别(含源码)
  17. box-shadow 设置单边、多边阴影
  18. 意念控制四旋翼 学习笔记
  19. 创业故事:记YouTube创始人陈士骏,选择满意工作,让自己人生无悔
  20. WebServer项目介绍

热门文章

  1. android 4.4 mtk 默认滑动解锁改为假指纹解锁
  2. 黑帽SEO都有哪些作弊手法?
  3. project professional 2007 连接不上project server 2007
  4. 中产学院第281期全国医学产康技术财富论坛暨无忧轻创合伙人大会
  5. 51nod 1455:宝石猎人
  6. 苏宁式集权:苏宁帝国将如何攻城掠地?
  7. Linux | 文件比较 / vi编辑与使用 / 文件通配符
  8. 2022年全球市场掺硼电极总体规模、主要生产商、主要地区、产品和应用细分研究报告
  9. project 2013 删除资源
  10. 糖友吃苦瓜能降血糖嘛