北京大学MOOC C++学习笔记(二)类

类成员的可访问范围:

 在类的定义中,用下列访问范围关键字来说明类成员
可被访问的范围:
– private: 私有成员,只能在成员函数内访问
– public : 公有成员,可以在任何地方访问
– protected: 保护成员,以后再说

  • 如过某个成员前面没有上述关键字,则缺省地被认为是私有成员。 
  • 在类的成员函数内部,能够访问:
    – 当前对象的全部属性、函数;
    – 同类其它对象的全部属性、函数。
  • 在类的成员函数以外的地方,只能够访问该类对象的公有成员。

设置私有成员的机制,叫“隐藏”。
“隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员
函数即可。否则,所有直接访问成员变量的语句都需要修改。

成员函数的重载及参数缺省 :

  • 成员函数也可以重载。
  •  成员函数可以带缺省参数。
  • 使用缺省参数要注意避免有函数重载时的二义性。

构造函数:

成员函数的一种。
名字与类名相同,可以有参数,不能有返回值(void也不行)。
作用是对对象进行初始化,如给成员变量赋初值。
如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数。
默认构造函数无参数,不做任何操作。

  • 如果定义了构造函数,则编译器不生成默认的无参数的构造函数。
  •  对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数。
  •  一个类可以有多个构造函数,参数个数或类型不同。
  • 构造函数最好是public的,private构造函数不能直接用来初始化对象。

复制构造函数:

  •  只有一个参数,即对同类对象的引用。
  • 形如 X::X( X& )或X::X(const X &), 二者选一。后者能以常量对象作为参数
  • 如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
  • 不允许有形如 X::X( X )的构造函数。
class Complex {
public :
double real,imag;
Complex(){ }
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called

复制构造函数起作用的三种情况:

  • 当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句
  • 如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用。 
class A
{
public:
A() { };
A( A & a) {
cout << "Copy constructor called" <<endl;
} 
};

void Func(A a1){ }
int main(){
A a2;
Func(a2);
return 0;
}
  • 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用。 
class A
{
public:
int v;
A(int n) { v = n; };
A( const A & a) {
v = a.v;
cout << "Copy constructor called" <<endl;
}
};

A Func() {
A b(4);
return b;
}
int main() {
cout << Func().v << endl; return 0;
}

注意:对象间赋值并不导致复制构造函数被调用

class CMyclass {
public:
int n;
CMyclass() {};
CMyclass( CMyclass & c) { n = 2 * c.n ; }
};
int main() {
CMyclass c1,c2;
c1.n = 5; c2 = c1; CMyclass c3(c1);
cout <<"c2.n=" << c2.n << ",";
cout <<"c3.n=" << c3.n << endl;
return 0;
}
输出: c2.n=5,c3.n=10

在这里c2 = c1,是将c1中所有变量的值拷贝给c2,并不是c1和c2指向同一片空间。

常量引用参数的使用
void fun(CMyclass obj_ ) {
cout << "fun" << endl;
}
这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
所以可以考虑使用 CMyclass & 引用类型作为参数。
如果希望确保实参的值在函数中不应被改变,那么可以加上const 关键字:
void fun(const CMyclass & obj) {
// 函数中任何试图改变 obj 值的语句都将是变成非法
}

类型转换构造函数:

什么是类型转换构造函数?
定义转换构造函数的目的是实现类型的自动转换。
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。

class Complex {
public:
double real, imag;
Complex( int i) {// 类型转换构造函数
cout << "IntConstructor called" << endl;
real = i; imag = 0;
}
Complex(double r,double i) {real = r; imag = i; }
};
int main ()
{
Complex c1(7,8);
Complex c2 = 12;
c1 = 9; // 9 被自动转换成一个临时Complex 对象
cout << c1.real << "," << c1.imag << endl;
return 0;
}

转换构造函数的作用是将某种类型的数据转换为类的对象,当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。

#include <iostream>

using namespace std;
class A
{
public:
    int a;
    A(int a) :a(a) {}
    A reta()
    {
        return a;
    }
};
int main()
{
    A a(2);
    A b = a.reta();
    A c = 3;
    cout<<b.a<<"\n"<<c.a<<endl;
    return 0;
}

实际上这是由隐式转换机制造成的,如果不想要这种效果,可以在构造函数前加上explicit声明。加上之后上面的代码就会编译出错,提示无法从“int”转换为“Complex”。

既然能将数据转换为类型,类型也能转换为数据。c++的类型转换函数可以将一个类的对象转换为一个指定类型的数据。

扫描二维码关注公众号,回复: 3656088 查看本文章

类型转换函数的一般形式为 :

operator 类型名()

{实现转换的语句}

测试代码:

#include <iostream>

using namespace std;
class A
{
public:
    int a;
    A(int a) :a(a) {}
    operator int()
    {
        return a;
    }
};
int main()
{
    A a(2);
    int b = a + 3;
    A c = a + 4;
    cout<<b<<"\n"<<c.a<<endl;
    return 0;
}

析构函数:

什么是析构函数?

  1. 名字与类名相同,在前面加‘~’, 没有参数和返回值,一个类最多只能有一个析构函数。
  2. 析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
  3. 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
  4. 如果定义了析构函数,则编译器不生成缺省析构函数
class String{
private :
char * p;
public:
String () {
p = new char[10];
}
~ String () ;
};
String ::~ String()
{
delete [] p;
}

析构函数在对象作为函数返回值返回后被调用

class CMyclass {
public:
~CMyclass() { cout << "destructor" << endl; }
};
CMyclass obj;
CMyclass fun(CMyclass sobj ) { // 参数对象消亡也会导致析
// 构函数被调用
return sobj; // 函数调用返回时生成临时对象返回
}
int main(){
obj = fun(obj); // 函数调用的返回值(临时对象)被 
return 0; // 用过后,该临时对象析构函数被调用
}


输出:
destructor
destructor
destructor

构造函数和析构函数调用次数

Demo d1(1);
void Func()
{
static Demo d2(2);
Demo d3(3);
cout << "func" << endl;
}
int main () {
Demo d4(4);
d4 = 6;
cout << "main" << endl;
{ Demo d5(5);
}
Func();
cout << "main ends" << endl;
return 0;
}

输出结果: :
id=1 constructed
id=4 constructed
id=6 constructed
id=6 destructed
main
id=5 constructed
id=5 destructed
id=2 constructed
id=3 constructed
func
id=3 destructed
main ends
id=6 destructed
id=2 destructed
id=1 destructed

this指针:

其作用就是指向成员函数所作用的对象。

非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。

class Complex {
public:
double real, imag;
void Print() { cout << real << "," << imag ; }
Complex(double r,double i):real(r),imag(i)
{ }
Complex AddOne() {
this->real ++; //等价于 real ++;
this->Print(); //等价于 Print
return * this;
}
};

int main() {
Complex c1(1,1),c2(0,0);
c2 = c1.AddOne();
return 0;
} //输出 2,1

this指针和静态成员函数:
静态成员函数中不能使用 this 指针!
因为静态成员函数并不具体作用与某个对象!
因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数!

静态成员

静态成员:在定义前面加了static关键字的成员。

普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。

sizeof 运算符不会计算静态成员变量。

普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。

因此静态成员不需要通过对象就能访问。

如何访问静态成员
1) 类名::成员名
CRectangle::PrintTotal();
2) 对象名.成员名
CRectangle r; r.PrintTotal();
3) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
4) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;

静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
静态成员函数本质上是全局函数。
设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。

成员对象和封闭类:

有成员对象的类叫 封闭(enclosing)类。

任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的。
具体的做法就是:通过封闭类的构造函数的初始化列表。

class CTyre // 轮胎类
{
private:
int radius; // 半径
int width; // 宽度
public:
CTyre(int r,int w):radius(r),width(w) { }
};
class CEngine // 引擎类
{
};
54
class CCar { // 汽车类
private:
int price; // 价格
CTyre tyre;
CEngine engine;
public:
CCar(int p,int tr,int tw );
};
CCar::CCar(int p,int tr,int w):price(p),tyre(tr, w)
{
};
int main()
{
CCar car(20000,17,225);
return 0;
}

封闭类构造函数和析构函数的执行顺序:

  1. 封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数。
  2. 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
  3. 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。

封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象,也会用复制构造函数初始化。

class A
{
public:
A() { cout << "default" << endl; }
A(A & a) { cout << "copy" << endl;}
};
class B { A a; };
int main()
{
B b1,b2(b1);
return 0;
}

输出:
default
Copy

友元

友元分为友元函数和友元类两种:

1)  友元函数 : 一个类的友元函数可以访问该类的私有成员. 

2) 友元类 : 如果A是B的友元类,那么A的成员函数可以访问B的私有成员。

友元类之间的关系不能传递,不能继承。

常量成员函数:

如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加 const关键字。常量对象只能使用构造函数、析构函数和  有
const  说明的函数(常量方法)。

在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
常量成员函数内部不能改变属性的值,也不能调用非常量成员函数。

class Sample {
private :
int value;
public:
void func() { };
Sample() { }
void SetValue() const {
value = 0; // wrong
func(); //wrong
}
};
const Sample Obj;
Obj.SetValue (); // 常量对象上可以使用常量成员函数

在定义常量成员函数和声明常量成员函数时都应该使用const 关键字。

如果一个成员函数中没有调用非常量成员函数,也没有修改成员变量的值,那么,最好将其写成常量成员函数。

两个函数,名字和参数表都一样,但是一个是const,一个不是,算重载。

mutable成员变量
可以在const成员函数中修改的成员变量

class CTest
{
public:
bool GetData() const
{
m_n1++;
return m_b2;
}
private:
mutable int m_n1;
bool m_b2;
};

猜你喜欢

转载自blog.csdn.net/qq_25406563/article/details/82829454