笔记6 · this指针、静态成员数据、常成员数据、封闭类、对象数组、友元

一、C++对象数组

顾名思义,对象数组是一个数组,这个数组里的每个元素都是对象。

还是以Student类为例:

#include <iostream>
using namespace std;
class Student{
protected:
    int m_a;
    int m_b;
public:
//构造1
    Student():m_a(1), m_b(2)
    {cout<<"constructor1 was called"<<endl;}
 
//构造2
    Student(int a):m_a(a), m_b(1)
    {cout<<"constructor2.0 was called"<<endl;}
//构造3的写法和构造2达到了一样的效果
    Student(int a, int b)
    {
    	cout<<"constructor2.1 was called"<<endl;
        m_a = a;
        m_b = b;
    } 
//析构
    ~Student(){cout<<"destructor was called"<<endl;}
};

int main()
{
	cout<<"Step1"<<endl;
	Student arr[2];
	cout<<"- - - - - - - - - - - - - - - - - -"<<endl;
	cout<<"Step2"<<endl;
	Student arr2[2] = {4, 5};
	cout<<"- - - - - - - - - - - - - - - - - -"<<endl;
	cout<<"Step3"<<endl;
	Student arr3[2] = {3};
	cout<<"- - - - - - - - - - - - - - - - - -"<<endl;
	cout<<"Step4"<<endl;
	Student *parr = new Student[2];

	delete[] parr;
	return 0;
}

结合C语言数组的知识,即可一目了然的看出了他们分别是怎么初始化数组,并知晓如何调用构造函数的。

在构造函数拥有多个参数时,数组的初始化列表中要显示地包含构造函数的调用。

二、this 指针

this是C++中的一个关键字,也是一个const指针,它指向当前对象,通过它可以访问到当前对象。

#include <iostream>
using namespace std;

class Student{
public:
    void setname(char *name);
    void setage(int age);
    void setscore(float score);
    void show();
private:
    char *name;
    int age;
    float score;
};

void Student::setname(char *name){
    this->name = name;
}
void Student::setage(int age){
    this->age = age;
}
void Student::setscore(float score){
    this->score = score;
}
void Student::show(){
    cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
}

int main(){
    Student *pstu = new Student;
    pstu -> setname("李华");
    pstu -> setage(16);
    pstu -> setscore(96.5);
    pstu -> show();

    return 0;
}

注意this是一个指针,要用->来访问成员数据。

this用在类的内部,但是只有在对象被创建以后才会给this赋值,并且这个赋值过程是编译器自动完成的;不需要用户干预,用户也不能显式地给this赋值

this本质上是成员数据的一个隐式形式形参,在调用成员函数将对象的地址作为实参传递给this。是一个局部变量,所以只能用在类的内部,并且有通过对象调用成员时才给this赋值。

几点注意:

1. this是const指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是非法的

2. this只能在成员函数的内部使用,用在其他地方是没有意义,也是非法的。

3.只有当对象实例化之后,this才有意义,因此不能在static成员函数中使用(因为static成员函数没有this指针)。

三、C++常成员数据

常成员数据分为:常成员变量、常成员函数、常成员对象(在封闭类中,对象组合中使用)

常成员变量:使用const修饰;表示创建对象时只被创建一次,且只能访问不能修改;常见得:

const int a; = int const a;

const *int a;  ! = int cont *a;

只有:const int& obj;  注意:没有 int& const obj;

const成员变量用法和普通const变量的用法类似,只需要在声明时加上const关键字。初始化const成员只有一种方法:就是通过构造函数的初始化列表。在前边的构造函数参数列表中已经总结过了。

常成员函数(const成员函数)

这里总结几个注意点:

1. const成员函数可以访问类中的所有的成员变量,但不能修改他们的值。

2. const成员函数不能调用非const成员函数,只能调用其他的const成员函数。

3. 需要强调的是必须在成员函数的声明和定义处同时加上const关键字。

4. 函数开头的const用来修饰函数的返回值,表示返回值是const类型,是不可以被修改的一块内存;

例如:const get_char();

5. C++成员函数头部的结尾处空一个空格再加上const关键字加以修饰,表示这种函数只能读取成员变量的值,而不能修改他们;

例如:char *get_string() const;

6. 同名的非const成员对象优先调用非const同名成员成员函数;如果真的没有同名的(与const成员函数),那么就才能调用同名得const成员函数。而const成员对象,只能调用同名的const成员函数

如下代码所示:

#include <iostream>
using namespace std;

class base{
private:
    int m_a;
    int m_b;

public:
    base(int a, int b): m_a(1), m_b(2)
    {
	this->m_a = a;
	this->m_b = b;
    }
    base(): m_a(1), m_b(2){}

    void print(void);
    void print(void) const;
    ~base()
    {
	cout<<"i am a destuctor"<<endl;
    }
};

void base::print(void)
{
    cout<<"i am not const identify"<<endl;
    cout<<"m_a = "<<m_a<<endl;
    cout<<"m_b = "<<m_b<<endl;
}

void base::print(void) const
{
    cout<<"i have const identify"<<endl;
    cout<<"m_a = "<<m_a<<endl;
    cout<<"m_b = "<<m_b<<endl;
}

int main(void)
{
    const base b1(3, 4);
    base b2;

    b1.print();
    b2.print();
	
    return 0;
}

常成员对象:

上面提到了,常成员对象,我们只要定义了常成员对象只能调用const修饰的成员了。顾名思义,用const修饰对象,称为常对象;因为非const成员可能会修改对象的数据(C++禁止这样做)。

当然以前学过的const 指针,在这里仍然受用甚至是加强了! 例如:const对象指针。他们都只能调用const成员函数。

四、C++成员对象和封闭类:

在类中,如果一个类的成员变量是由另一个类创建出来的对象,那么本类中这样的成员就称为成员对象。包含成员对象的类就叫做封闭类。

还必须注意的一点就是我们在声明和定义类的成员对象的时候,必须注意他们的顺序和类体外定义函数在整个文件的顺序。要符合编译器向下寻找的顺序

如下所示,为自己总结的典型案列

#include <iostream>
using namespace std;

#if 0
如果一个类的成员变量是由另一个类创建出来的对象
那么本类的成员变量就会称为:成员对象。
包含成员对象的类叫做---封闭类
#endif

class A
{
    private:
	const int m_a;//常量必须初始化,且不能在private中初始化
	int m_b;
    public:
	A(int a, int b) : m_a(1)//A类的构造函数
	{
	    cout<<"i am class A constructor"<<endl;
	    m_b = b;
	}

	void show1()
	{
	    cout<<"m_a = "<<m_a<<endl;
	    cout<<"m_b = "<<m_b<<endl;
	}
	~A()
	{
	    cout<<"i am class A destructor"<<endl;
	}
		
};

class B
{
    private:
	A x_a;//由A创建的成员对象
	int b_b;
    public:
        B(int a, int b) : x_a(1, 3)//时用参数列表初始化成员对象x_a
	{
	    cout<<"i am class B constructor"<<endl;
	    b_b = a;
	}
	~B()
	{
	    cout<<"i am class B destructor"<<endl;
	}

	void show2()
	{
	    x_a.show1();
	    cout<<"b_b = "<<b_b<<endl;
	}			
};

class C
{
    private:
	B b_a;//由B创建的成员对象

	int c_b;
    public:
	C(int a, int b): b_a(2,3)
	{
	    cout<<"i am class C constructor"<<endl;
	    c_b = a;
	}

	~C()
	{
	    cout<<"i am class C destructor"<<endl;
	}
	void show3()
	{
	    b_a.show2();//使用成员对象向上调用Class B的方法
	    cout<<"c_b = "<<c_b<<endl;
	}
};

class D
{
    private:
        C c_a;//由C创建的成员对象
	B b_a;//由B创建的成员对象
		
	int d_b;
	int d_c;

    public:

	int tmp;
	D(int a, int b, int c) : b_a(a,b), c_a(4, 5)//交换他们的位置,则不影响他们的输出和层级调用顺序
	{
	    cout<<"i am class D constructor"<<endl;
	    d_b = b;
	    d_c = c;
			
	}

	D(const D &s) : c_a(4, 5), b_a(2, 3)
	{
	    cout<<" i am copyconstructor "<<endl;
			
	    this->d_b = s.d_b;
	    this->d_c = s.d_c;
	}

	~D()
	{
	    cout<<"i am class D destructor"<<endl;
	}

	void show4()
	{
	    c_a.show3();
	    b_a.show2();
	    cout<<"d_b = "<<d_b<<endl;
	    cout<<"d_c = "<<d_c<<endl;
			
	}
};

int main()
{	
    //const D obj(3, 4, 5);//这两种书写格式均是正确的
    D const obj(3, 4, 5);//首先将调用 D类的构造函数  
    D objc = obj;//const表明不能通过该对象修改成员数据

    //obj.tmp = 2;//会报只读错误
/*
    cout<<"obj.tmp = "<<obj.d_b<<endl;
    cout<<"objc.tmp = "<<objc.d_b<<endl;
*/
    objc.show4();
	
    return 0;
}

我们来分析下它的调用顺序:

首先我们从最底层的D类开始创建的对象,它会依次向上调用相应的构造函数;到达上方的构造时,发现其构造仍然依赖于在其又一上面的类或上上级类,构造时仍需要向上寻找,将上级所有的依赖以及本身均创建完毕时,才能创建它本身。最后析构的顺序完全和构造相反,故构造和析构的运行结果是对称的。

总结

1.  创建封闭类的对象时,其包含的成员对象也必须被创建。这就会引发成员对象对构造函数调用。

2.  对于基本类型的成员变量,参数表中只有一个值,就是初始值,在调用构造函数时,会把这个初始值直接赋值给这个变量。

3.  但对于成员对象,“参数表”中存放的欲调用的构造函数的参数,它可能是一个值,也可能是多个值,更可能是无值。因为欲调用的构造函数可能是有参函数,也有可能是无参构造函数它指明了该成员对象该如何被初始化

总之,生成封闭类对象的语句,就一定要让编译器弄明白其成员对象是如何初始化的,否则就会编译错误。

构造函数运行的次序,于其在构造函数初始化列表中的出现的次序无关

五、static静态成员和static静态成员函数:

revise:

C语言中的static:

我们知道,全局变量和函数的作用域默认是整个程序,也就是所有的源文件,这给程序的模块化开发带来了很大方便,让我们能够在模块 A 中调用模块 B 中定义的变量和函数,而不用把所有的代码都集中到一个模块。
但这有时候也会引发命名冲突的问题,例如在 a.c 中定义了一个变量 n,在 b.c 中又定义了一次,链接时就会发生重复定义错误,原因很简单,变量只能定义一次。
如果两个文件都是我们自己编写的或者其中一个是,遇到这样的情况还比较好处理,修改变量的名字即可;如果两个文件都是其他程序员编写的,或者是第三方的库,修改起来就颇费精力了。
实际开发中,我们通常将不需要被其他模块调用的全局变量或函数用 static 关键字来修饰,static 能够将全局变量和函数的作用域限制在当前文件中,在其他文件中无效

static局部变量

static 除了可以修饰全局变量,还可以修饰局部变量,被 static 修饰的变量统称为静态变量(Static Variable)。不管是全局变量还是局部变量,只要被 static 修饰,都会存储在全局数据区(全局变量本来就存储在全局数据区,即使不加 static)。全局数据区的数据在程序启动时就被初始化,一直到程序运行结束才会被操作系统回收内存;对于函数中的静态局部变量,即使函数调用结束,内存也不会销毁。注意:全局数据区的变量只能被初始化(定义)一次,以后只能改变它的值,不能再被初始化,即使有这样的语句,也无效。

总结起来两点:

1) 隐藏

程序有多个模块时,将全局变量或函数的作用范围限制在当前模块,对其他模块隐藏。

2) 保持变量内容的持久化

将局部变量存储到全局数据区,使它不会随着函数调用结束而被销毁。


C++中static当然是完全兼容了C中的static关键字了;并且引入了类和对象后,这使得我们在编程的时候逻辑更加严谨。

对象的内存中包含了成员变量,不同的对象占用不同的内存空间,这使得不同对象的成员变量相互独立,它们的值不受其他对象的影响。

有时候我们希望在多个对象之间共享一些数据,就是临界资源。

使用静态成员可以实现多个对象之间共享某些数据的目的

static修饰的变量,必须在类体外初始化,且只被初始化一次

语法:<typename> <classname> :: <name> = value;

1.  注意点static成员变量的内存不占用对象的内存,也不在声明的时候分配,而是在程序启动时初始化在静态数据段内的,和全局数据区性质类似,直到程序运行结束后才被销毁。没有在类外初始化的static成员变量不能使用

2. 访问方式

//通过类名访问
Student :: m_tatal = 10;
//通过对象名来访问
Student stu("小明", 15, 88);
stu.m_total = 20;//注意他们受访问权限的控制
//通过对象指针来访问
Student *pstu = new Student("小芳", 19, 96);
pstu->m_total = 20;//静态变量可以访问,可以修改

//以上三种方式等效

·  int Student::m_total = 0; 等价于 int Student::m_total;

因为只有全局(静态)数据区段的变量默认的初始化值为 0,而动态数据段(堆、栈)的变量默认值才是不确定的垃圾值。

访问静态成员变量时,要完全遵循访问权限,和继承访问权限的限制,也就是声明作用域的问题

举个例子:

#include <iostream>
using namespace std;

class base{
private:
    int m_a;
    static int m_b;

public:
    static int m_c;
    base(int a, int b, int c)
    {
        m_a = a;
	m_b = b;
	m_c = c;
    }
    base():m_a(0){}
	
    ~base(){cout<<"Destructor was called"<<endl;}

    void show(void);
};

int base:: m_b = 1;//静态成员必须有初始化动作
int base:: m_c = 2;

void base::show(void)
{
    cout<<"a = "<<m_a<<endl;
    cout<<"b = "<<m_b<<endl;
    cout<<"c = "<<m_c<<endl;
}

int main(void)
{
    cout<<"static m_c = "<<base::m_c<<" "<<endl;

    base *pobj = new base(3, 4, 5);
	
    cout<<"static m_c = "<<pobj->m_c<<" "<<endl;
	
    pobj->show();
	
    delete pobj;
    return 0;
}


关于静态成员函数

静态成员函数只能访问静态成员。编译器在编译一个普通成员变量/函数时会隐式的向对象中增加一个this指针,并把当前对象的地址赋值给this,所以普通成员函数只能在创建对象后,通过实例化的对象来访问。因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。

普通成员变量占用对象的内存,静态成员函数没有 this 指针,不知道指向哪个对象,无法访问对象的成员变量,也就是说静态成员函数不能访问普通成员变量,只能访问静态成员变量。
普通成员函数必须通过对象才能调用,而静态成员函数没有 this 指针,无法在函数体内部访问某个对象,所以不能调用普通成员函数,只能调用静态成员函数。
静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)

例子:

#include <iostream>
using namespace std;

void func()
{
    cout<<"func was called"<<endl;
}

class computer{
private:
    int price;
    static int total;

public:
	
    computer():price(5000){total += price;}
	
    computer(int a)
    {
	price = a;
	total += price;
    }

    static void show();

    static void boost();
	
    ~computer()
    {
        cout<<"Destructor was called"<<endl;
    }
};

int computer::total = 1;

void computer:: boost()
{
    cout<<"i am boost"<<endl;
}

void computer :: show()
{
    func();
    cout<<"The computer tatlly price is "<<total<<" "<<endl;
}


int main()
{
    computer obj;
    obj.show();

    computer::boost();//通过类名访问
	
    computer *pobj = new computer(6000);

    pobj->show();

    computer *ppobj = new computer(2000);
    ppobj->show();
	
    delete pobj;
    delete ppobj;

    return 0;
}

C++中,静态成员函数的主要目的是访问静态成员。getTotal()、getPoints() 当然也可以声明为普通成员函数,但是它们都只对静态成员进行操作,加上 static 语义更加明确。
和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。静态成员函数可以通过类来调用(一般都是这样做),也可以通过对象来调用。


六、友元函数、友元成员函数、友元类:

C++ 中,一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。现在,我们来介绍一种例外情况——友元(friend)。借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。


friend 的意思是朋友,或者说是好友,与好友的关系显然要比一般人亲密一些。我们会对好朋友敞开心扉,倾诉自己的秘密,而对一般人会谨言慎行,潜意识里就自我保护。在 C++ 中,这种友好关系可以用 friend 关键字指明,中文多译为“友元”,借助友元可以访问与其有好友关系的类中的私有成员。如果你对“友元”这个名词不习惯,可以按原文 friend 理解为朋友。

友元函数

在当前类以外定义的、不属于当前类的函数也可以在类中声明,但要在前面加 friend 关键字,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。

友元函数可以访问当前类中的所有成员,包括 public、protected、private 属性的。

#include <iostream>
using namespace std;

class Student{
public:
    Student(char *name, int age, float score);
public:
    friend void show(Student *pstu);  //将show()声明为友元函数
private:
    char *m_name;
    int m_age;
    float m_score;
};

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }

//非成员函数
void show(Student *pstu){
    cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
}

int main(){
    Student stu("小明", 15, 90.6);
    show(&stu);  //调用友元函数
    Student *pstu = new Student("李磊", 16, 80.5);
    show(pstu);  //调用友元函数

    return 0;
}

运行结果:

小明的年龄是 15,成绩是 90.6
李磊的年龄是 16,成绩是 80.5

friend 函数不仅可以是全局函数(非成员函数),还可以是另外一个类的成员函数。

例子:

#include <iostream>
using namespace std;

class Boy;//声明类B

class Man{
private:
    int m_a;
    int m_b;

public:
    Man():m_a(1), m_b(2){}
    Man(int a, int b)
    {
	m_a = a;
	m_b = b;
    }

    void Girl(Boy &b);//将友元函数声明在类体之中
	
    ~Man()
    {
        cout<<"I am Man's Destructor"<<endl;
    }
};


class Boy{
public:
    int age;
    float tall;

public:
    Boy():age(20), tall(172.5){}
    Boy(int a, float b)
    {
	age = a;
	tall = b;
    }

    void show();
	
    friend void Man :: Girl(Boy &b);//采用引用的方法传递
//对外共享
    friend void Dog(Boy *d);//采用指针的方法传递
	
    ~Boy()
    {
	cout<<"Destructor was called"<<endl;
    }
};


void Man :: Girl(Boy& b)
{
	cout<<"The girl share boy age is:"<<b.age<<" "<<endl;
	cout<<"The girl share boy tall is:"<<b.tall<<" "<<endl;
}

void Dog(Boy *d)
{
//友元函数可以是不属于任何类非成员的函数, 也可以是其他类的成员函数,如下所示
	//cout<<"The dog share boy info is:"<<d->show()<<" "<<endl;//所以它不能访问类体数据
	cout<<"The dog share boy age is:"<<d->age<<" "<<endl;
	cout<<"The dog share boy tall is:"<<d->tall<<" "<<endl;
}


void Boy::show()
{
	cout<<"The boy age is:"<<age<<" "<<endl;
	cout<<"The boy tall is:"<<tall<<" "<<endl;
}


int main()
{
	Man obj1;//栈区对象1 使用构造1
	Boy obj2(23, 174.45);//栈区对象2 使用构造2
	Boy *obj3 = new Boy(24, 175.88);//堆区对象3 使用构造2
	Man *obj4 = new Man(1997, 8);
	
	//obj1.show();
	obj1.Girl(obj2);//只有用Man创建出来的对象才能调用Girl
	//obj1.Dog(&obj2);

	obj2.show();
	//Girl(obj2);//如果友元函数是其他类的成员函数 就不能直接调用 而是通过对象调用
	Dog(&obj2);

	obj3->show();
	//Girl(*obj3);
	Dog(obj3);

	obj4->Girl(*obj3);//Girl实际传入的参数是Boy创建出来的对象
	//obj4->Dog(obj3);
	
	delete obj3;
	delete obj4;
	
	return 0;
}


友元类:

不仅可以将一个函数声明为一个类的“朋友”,还可以将整个类声明为另一个类的“朋友”,这就是友元类。友元类中的所有成员函数都是另外一个类的友元函数。
例如将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性的。

#include <iostream>
using namespace std;

class Address;  //提前声明Address类

//声明Student类
class Student{
public:
    Student(char *name, int age, float score);
public:
    void show(Address *addr);
private:
    char *m_name;
    int m_age;
    float m_score;
};

//声明Address类
class Address{
public:
    Address(char *province, char *city, char *district);
public:
    //将Student类声明为Address类的友元类
    friend class Student;
private:
    char *m_province;  //省份
    char *m_city;  //城市
    char *m_district;  //区(市区)
};

//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
    cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}

//实现Address类
Address::Address(char *province, char *city, char *district){
    m_province = province;
    m_city = city;
    m_district = district;
}

int main(){
    Student stu("小明", 16, 95.5f);
    Address addr("陕西", "西安", "雁塔");
    stu.show(&addr);
   
    Student *pstu = new Student("李磊", 16, 80.5);
    Address *paddr = new Address("河北", "衡水", "桃城");
    pstu -> show(paddr);

    return 0;
}

关于友元,有两点需要说明:

  • 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
  • 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。

除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值