嘉明的C++学习(2)之类与对象
类与对象的概念
对象是现实世界中实际存在的事物,是构成世界的一个独立单位,它由数据(描述事物的属性)和作用于数据的操作(体现事物的行为)构成一个独立整体。
我们看待事物的方法就是面向对象的,比如人具有姓名、性别、年龄、身高等属性,也具有吃饭、睡觉、说话等行为,这种把属性和操作方法放在一起而形成的一个相互依存又不可分割的整体,再找出同类型对象的共有属性和行为的过程称为抽象。抽象是面向对象编程思想的本质,而类是其关键。
在面向对象的方法中,类是具有相同属性和行为的一组对象的集合,它提供一个抽象的描述,其内部包括**属性和行为(功能)**两个主要部分。
(1)类的构成与定义
也就是Java中的成员变量与成员方法。
成员方法(函数)在类里定义:
class Num{
private:
int i;
public:
int j;
void give(int ii,int jj){//在类里定义
i=ii;
j=jj;
}
};
成员方法(函数)在类外定义:
class Num{
private:
int i;
public:
int j;
void give(int ii,int jj);
};
void Num::give(int ii, int jj){//在类外定义
i = ii;
j = jj;
}
注:成员方法(函数)在类外定义时,格式:数据类型 类名::对象名(参数列表){}
在定义类结束后,记得加上;
注:private 与 protected类只能在类中访问,不能在类外访问,如果对类不做定义则默认为private类。
代码演示:
#include<iostream>
using namespace std;
class Num{
private:
int i;
public:
int j;
void give(int ii,int jj){
i=ii;
j=jj;
}
};//记得加上;
int main(){
Num n;
n.give(1,2);
cout<<n.j;//public,可以访问
cout<< n.i;//private,不能访问
return 0;
}
(2)对象的定义与使用
代码举例:
class Num{
private:
int i;
public:
int j;
void give(int ii,int jj);
};
比如这里,i是一个对象,它将占有系统分配int数据类型的空间
图解,每个同类对象都通过指针指向一个公共代码区
对象指针
*类名 指针变量名,Student p / /Student类的指针
Student stu / /Sudent类的对象
p = &stu / /使指针指向对象stu
使用(访问)对象的方法
(3)类的使用域和成员属性
因为同类对象使用同一个公共代码区,因此其在类中的成员数据可以相互访问
在类中,成员可以互相访问,与private、public、protected无关。
但在类外时,private型的数据不可以访问,protected型的数据需要满足派生类的条件。public型的可以通过使用对象名字来访问。
是构造函数,不是结构体
#include<iostream>
#include<string>
using namespace std;
class S{
public:
S(int p1 , string p2 , int p3 );
void show();
int number1;
private:
string number2;
int number3;
};
S::S(int p1 , string p2 , int p3){
number1 = p1;
number2 = p2;
number3 = p3;
}
void S::show(){
cout<<number1<<endl<<number3<<endl<<number2<<endl;
}
int main(){
S s1(1,"abc",3);
s1.show();
cout<<s1.number1<<endl;
system("pause");
return 0;
}
构造函数
构造函数特点:(1)与类同名字的
(2)执行在创建对象时自动执行
(3)无返回值,定义时无需void
(4)若没有定义,系统会自动生成一个默认的构造方法
(5)定义位置与一般函数一致
这是一般的没有构造函数的学生类代码,它在定义对象的同时不能进行赋值操作,因此定义一个构造函数可以提高其便捷性。
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
void set(int a , string b , int c){
no = a; name = b; age = c;
}
void show(){
cout<< no << endl << name << endl <<age <<endl;
}
private:
int no;
string name;
int age;
};
int main(){
Student s;
s.set(001,"胡晶",100);
s.show();
return 0;
}
下面是加了构造函数的代码
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(){
cin >> no >> name >> age;
}
void show(){
cout<< "工号:" <<no << endl << "名字:" <<name << endl << "年龄:" <<age <<endl;
}
private:
string no;
string name;
int age;
};
int main(){
Student s;
s.show();
return 0;
}
其在定义对象的同时可以给函数赋值。增加了编写函数的效率
结果:
构造函数的重载
其意思与方法重载一个意思。
就是名字相同(虽然都是类的名字)但参数名字不同。
上代码:
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(){
cin >> no >> name >> age;
}
Student(string a , string b ,int c){
no = a; name = b; age = c;
}
void show(){
cout<< "工号:" <<no << endl << "名字:" <<name << endl << "年龄:" <<age <<endl;
}
private:
string no;
string name;
int age;
};
int main(){
Student s1;
Student s2("0025","小明",3);
s1.show();
cout<<endl;
s2.show();
return 0;
}
注:上面无参的构造函数,就是没有定义,系统也会默认给出其构造方法。
与返回类型无关
至于调用哪个构造函数,主要看你在主函数输出的形式是怎么样的,和方法重载中不同的数据类型调用不同的方法差不多。
析构函数
析构函数的特点:
(1)格式:~类名(){}
(2)用于清理动态的内存,简单来说就是delete功能,要new才可以delete
(3)一个类中只能有一个
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(){
cin >> no >> name >> age;
}
Student(string a , string b ,int c){
no = a; name = b; age = c;
}
void show(){
cout<< "工号:" <<no << endl << "名字:" <<name << endl << "年龄:" <<age <<endl;
}
~Student(){
cout<<"输出:"<<age<<endl;
}
private:
string no;
string name;
int age;
};
int main(){
Student s1;
Student s2("0025","小明",3);
s1.show();
cout<<endl;
s2.show();
cout<<endl;
return 0;
}
这里是为了展示第四点,顺序相反,s2后进但在析构函数先出。
拷贝构造函数
拷贝构造函数将现有的对象数据成员依次拷给新对象对应成员中,实现赋值,即数据初始化功能
功能图解:
year(y)的意思是将year = y,不能在中括号里面定义。
#include<iostream>
using namespace std;
class S{
public:
S(int y,int x):no(x),age(y){}
void show(){
cout<<"no:"<<no<<endl<<"age:"<<age<<endl;
}
private:
int no;int age;
};
int main(){
S s1(10,18);
s1.show();
S s2(s1);//复制构造函数,S s2 = s1
s2.show();
return 0;
}
#include<iostream>
#include<string>
using namespace std;
class Date{
public:
Date(int y,int m,int d):year(y),month(m),day(d){
}
void show(){
cout<<"y = "<<year<<" "<<"m = "<<month<<" "<<"day = "<<day<<endl;
}
Date(const Date & d){
cout<<"**********************"<<endl;
cout<<"启动复制函数"<<endl;
year = d.year;
month = d.month;
day = d.day;
}
private:
int year;
int month;
int day;
};
int main(){
int y;
int m;
int d;
cout<<"请输入数字"<<endl;
cout<<"y = ";
cin>>y;
cout<<"m = ";
cin>>m;
cout<<"d = ";
cin>>d;
Date d1(y,m,d);
d1.show();
Date d2 = d1;
cout<<"复制函数为"<<endl;
d2.show();
cout<<"----------------------"<<endl;
return 0;
}
复制构造函数 类名(const 类名 & 变量名)
这种有函数的区别和Date d2(d1)这种区别就是,类名(const 类名 & 变量名)可以使复制内容其他之余使代码更有多样化。而Date d2(d1)只可以复制不可添加其他内容,比较单一,功能较少。
深拷贝与浅拷贝
浅拷贝:就是指简单的复制,不怎么定义复制构造函数。
深拷贝:需要用到打开文件、网络连接、动态内存申请、指向其他数据…这些就属于“深拷贝”
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(int m, char *y,int a){
mark = m;
name = new char[strlen(y)+1];strcpy(name,y);
age = a;
}
void show(){
cout<<"mark = "<<mark<<" "<<"name = "<<name<<" "<<"age = "<<age<<endl;
}
~Student(){
if(name != NULL)
delete []name;
}
private:
int mark;
char *name;
int age;
};
int main(){
Student d1(100,"李四",18);
d1.show();
Student d2(d1);
d2.show();
return 0;
}
结果:显示报错
为什么会产生报错呢?
因为当执行 Student d2(d1)时,d2就指向了d1的数据空间公用同一个数据空间,而不是给d2多开辟一个空间系统,(这种简单的复制叫做 浅复制 ),这就是它的缺点。
那么如何解决该问题呢?
这就要用到深复制了。
类名(const 类名 & 变量名)
代码如下
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(int m, char *y,int a){
mark = m;
name = new char[strlen(y)+1];strcpy(name,y);
age = a;
}
void show(){
cout<<"mark = "<<mark<<" "<<"name = "<<name<<" "<<"age = "<<age<<endl;
}
Student(const Student & d){
mark = d.mark;
name =new char[strlen(d.name)]; //这里是错误的应该是(name = new char[strlen(d.name) + 1]),否则系统会报错
strcpy(name,d.name);
age = d.age;
}
~Student(){
if(name != NULL)
delete []name;
}
private:
int mark;
char *name;
int age;
};
int main(){
Student d1(100,"李四",18);
d1.show();
Student d2(d1);
d2.show();
return 0;
}
但是这样还是会报错,但是原因不是上面,而是你在复制构造函数中开辟的空间不够
因此要记得加上 1,程序才正确
name =new char[strlen(d.name)];
//改为
name = new char[strlen(d.name) + 1]
这就是所谓的深拷贝,拷贝构造函数自己有一个空间(申请动态空间)。
特点
1、类形参只有一个
2、每一个类至少有一个
自引用指针this
类中的成员变量都有一个特点他们有共用同一段内存空间存放函数代码,在形形色色的代码块中,他们是如何准确的调用自己的对象数据的呢?
这时候this函数就出来了
this的作用是指向对象的地址 ,简单来说 this = 对象地址,用于成员函数。
#include<iostream>
#include<string>
using namespace std;
class Student{
public:
Student(int m){
mark = m;
}
void println(){
cout<<"mark = "<<mark<<" "<<"this = "<<this<<endl;//mark = this->mark。证明了this是地址。
}
void println2(){
int a = 10;
cout<<"a的地址:"<<&a<<endl;
}
private:
int mark;
};
int main(){
Student d1(100);
d1.println();
d1.println2();
cout<<"***********************"<<endl;;
Student d2(102);
d2.println();
d2.println2();
return 0;
}
结果:
类的特殊成员:对象成员与静态成员
类的特殊成员:对象成员与静态成员
成员1、成员2、成员3…成员n分别是是类1、类2、类3…类n的对象。
#include<iostream>
#include<string>
using namespace std;
class Score{
public:
Score(double c = 0,double e = 0){
computer = c; english = e;
}
void show(){
cout<<"computer's score:"<<computer<<endl<<"english's score:"<<english;
}
private:
double computer;
double english;
};
class Student{
public:
Student(string o,int a,string n,double s1,double s2):score1(s1,s2){//这里可以调用score类,赋值给它。
no = o;
age = a;
name = n;
}
void show(){
cout<<"no:"<<no<<endl;
cout<<"age:"<<age<<endl;
cout<<"name:"<<name<<endl;
score1.show();//直接用score类的内容
cout<<endl;
}
private:
int age;
string name;
string no;
Score score1;//要声明创建了Score类的对象
};
int main(){
Student s("001",18,"胡某",150,150);
s.show();
return 0;
}
静态成员
静态的数据成员不与类中的对象处于同一空间,其不属于任何一个对象而是属于类。
这说明数据是共享的
#include<iostream>
#include<string>
using namespace std;
class File{
public:
File(string name1,int m = 0){
name = name1;
mark = m;
++count;
sum+=mark;
average = sum/count;
}
void show1(){
cout<<name<<" "<<mark<<endl;
}
static void show2(){
cout<<" 累加成绩:"<<sum<<endl<<" 总人数:"<<count<<endl<<" 平均分:"<<average<<endl;
}
private:
static int count;
static int sum;
static double average;
string name;
double mark;
};
int File::count = 0;
double File::average = 0.0;//给静态成员赋值
int File::sum = 0;
int main(){
File s1("张三",100),s2("李四",100),s3("赵琦",150);
s1.show1();s2.show1();s3.show1();
File::show2();//调用静态函数成员
system("pause");
return 0;
}
友元函数和友元类
#include<iostream>
#include<cstring>
using namespace std;
class Student{
private:
char name[10];
char num[10];
friend void show(Student& st){//表面st代表Student类
cout<<"Name:"<<" "<<st.name<<"\n";
}
public:
Student(char *s1,char *s2){//strcpy不能用string,所以用了char指针。
strcpy(name,s1);
strcpy(num,s2);
}
};
class Score{
private:
unsigned int mat,pht,eng;
friend void show_all(Student& st,Score* sc){//创建对象指针
show(st);//引用Student中的友元函数
cout<<"mathematics:"<<sc->mat
<<"\nPhyics:"<<sc->pht
<<"\nEnglish:"<<sc->eng<<endl;
}
public:
Score(unsigned int i1,unsigned int i2,unsigned int i3):mat(i1),pht(i2),eng(i3)
{}//用unsigned防止出现负数
};
int main(){
Student wang("Wang","2019");
Score ss(72,82,92);
show_all(wang,&ss);//调用友元函数(对象名字,对象地址(因为定义的是指针)),不用加对象名字
return 0;
}
第二种表达方式(在其他类中声明本类是它的朋友)
这里是在Student中说明Score是它的朋友
这里的void show(Stuent&);声明之后就可以对Student中数据的一些引用。
先对要引用的对象进行声明
这里在Student声明Score是它的朋友
注:这里的类名是Score但是参数时Student的
总结:友元函数不属于类的内容,静态变量不属于对象内容,因此要调用时要注意规则。
在main函数里
直接用 函数名 调用友元函数。
用 类名 +函数名 调用静态函数。
(都不用对象.)
友元类
#include<iostream>
#include<cstring>
using namespace std;
class Student{
friend class Score;
char name[15],num[15]; //声明Score是Student的友元类
public:
Student(char *s1,char *s2){
strcpy(name,s1);
strcpy(num,s2);
}
};
class Score{
public:
Score(unsigned int i1,unsigned int i2,unsigned int i3):eng(i1),mat(i2),phy(i3){}
void show(){
cout<<"\n英语:"<<eng
<<"\n数学:"<<mat
<<"\n物理:"<<phy<<endl;
}
void show2(Student&);
private:
unsigned int eng;
unsigned int mat;
unsigned int phy;
};
void Score::show2(Student& st){
cout<<"name:"<<st.name;
cout<<"\nnum:"<<st.num;
}
int main(){
Student pan("潘潘","2020");
Score ss(150,150,150);
ss.show2(pan);
ss.show();
system("pause");
return 0;
}
数据共享与保护
static可共享不过只能在类中访问,而const可以在其他类中访问。
可以做到共享而不被篡改
常对象成员
#include<iostream>
using namespace std;
class Test{
public:
Test(int x,int y){
a = x;b = y;
}
Test(){}
void show()const{
cout<<"a = "<<a
<<" "
<<"b = "<<b<<endl;
}
void set(int aa,int bb){
a = aa; b = bb;
}
private:
int a,b;
};
int main(){
const Test t1(1,2);
Test t2;
t2.set(5,6);
t1.show();
t2.show();
cout<<"*******************"<<endl;
t2.set(50,60);
t1.show();
t2.show();
return 0;
常成员函数
系统会根据对象定义是否有const调用对应函数
所以 1调用的是const函数
2调用的是普通函数
普及引用: 为引用对象,若给a赋值。a = 1,这也等价于给b赋值,b = 1,所以这两个相当于合为一体,当数字变换时,和他引用的也要变。**
可以返回一个引用值
错题集
用new时,一定要指明数组大小!!
new运算符:
(1)可以自动计算所需内存大小,然后再进行分配。
(2)运算符能够自动返回正确指针类型
arr是一个固定地址值,不能进行自我运算
经过了两次对换,引用对换为2211,地址再对换1122
内联函数不可以有for ,while等等循环语句,否则将会变为普通函数调用。
a 等价于 b,所以形式也要一样。
这里就设计了默认参数,若没有人为赋值,则默认吧默认参数的值给变量。
数据成员占有自己的空间,函数成员空间共享。
所以答案选D
常成员数据只能用构造函数定义。
内联不可以