一、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 的友元类。
除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。