在C++中,引出了类的定义,C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
1.在C++中,在结构体内部,不仅可以定义变量,还可以定义函数,只不过在C++中,更喜欢用class来代替strcuct。 那么就可以来按照结构体的框架来定义一个类了
struct默认的成员都是public的(因为struct要兼容C),class默认的成员都是private的
class StuInfor
{
public:
类方法
void Show()
{}
void GetName()
{}
void GetSex()
{}
void GetAge()
{}
类成员
char name[10];
char sex[3];
int age;
};//注意后面要加上分号
注意:类方法可以在类里面进行实现,也可以只在类里声明,在类外实现。只不过在类外实现时要加作用域访问符。
class StuInfor
{
public:
void Show();
void GetName();
void GetSex();
void GetAge();
类成员
char name[10];
char sex[3];
int age;
};
//在类外实现方法,要加上类名和作用域访问符::,这样才能说明是这个类的方法,而非其它类的方法
void StuInfor::Show()
{}
void StuInfor::GetName()
{}
void StuInfor::GetSex()
{}
void StuInfor::GetAge()
{}
类里面有三种修饰限定符,它们各自的作用域是从当前限定符到下一个限定符之间的所有成员
public:在类外,可以直接访问public修饰的成员
private:修饰的成员无法在类外被直接访问
protect:修饰的成员无法在类外被直接访问
所以,在类外,我们可以直接通过调用类里被public所修饰的方法来间接操控数据。
class StuInfor
{
public:
void GetInfor(char*name,char*sex,int age);
private:
char _name[10];
char _sex[3];
int _age;
};
void StuInfor::GetInfor(char*name,char*sex,int age)
{
strcpy(_name,name);
strcpy(_sex,sex);
_age = age;
}
void main()
{
StuInfor stu;
stu.GetInfor("张三","nan",20);//通过public方法来操纵数据.
}
2.一个类的大小
class StuInfor
{
public:
类方法
void Show()
{}
private:
类成员
char name[10];
char sex[3];
int age;
};
void main
{
StuInfor stu;
cout<<sizeof(stu)<<endl;//输出为8.
}
3.隐藏的this指针
C++编译器给每个成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。这些都是编译器自动完成的。
class Data
{
public:
SetData(int year,int month,int day)//函数的参数应该是SetData(Data *const this,int year,int month,int day)
{
this->m_year = year;
this->m_month = month;
this->m_day = day;
//this可以省略,所以一般都不写,但是要知道这个是有指针指向的。
}
private:
int m_year;
int m_month;
int m_day;
};
void main()
{
Data d;
d.SetData(1999,6,3);//实际上函数的参数应该是SetData(&d,1999,6,3);
}
注:
1.this指针的类型是什么?
答:类型为 类名 * const ,加const的原因是因为防止函数内部把this指针改变
2.this指针可以为空吗?
答:可以,如果函数内部没有用到this指针,比如只打印一条语句,是可以为空的,但是在函数内部,如果有用到this指针,或者有this指针所指向的内容,这时候this指针就不能为空。
3.this指针存在于哪?
答:this指针参数则是存放在寄存器中。
4.类的基本函数
①:构造函数:对对象进行初始化(不是创造对象)
1.函数名和类相同
2.无返回值
3.可以重载
4.定义对象时,如果传参了,调用有参数的构造函数,没传参调用没参数的构造函数,如果不写,系统默认调用自己的构造函数
class test
{
public:
test(int x,int y,int z):a(x),b(y),c(z)
{}
private:
int a ;
int b;
int c;
};
void main()
{
test t1(1,2,3);
}
注:除了上面赋值可以对对象进行初始化,还可以采用初始化列表的方式来进行初始化,在构造函数后加:,然后每个成员后面跟一个括号,括号里面放初始化的值或表达式,成员之间用逗号隔开
class test
{
public:
test(int x, int y,int z) :a(x), b(y), c(z)
{}
private:
int a;
int b;
int c;
};
void main()
{
test t1(1, 2, 3);
}
有几种成员只能通过初始化列表进行初始化
1.由const修饰的成员
2.由引用修饰的成员
3.初始化的成员是对象的时候,即成员是由另一个类来定义的时候只能采用初始化
class Date
{
public:
Date(int z) :_year(z)
{}
private:
int _year;
};
class test
{
public:
test(int x,int y,int z) :a(x), b(y), d(z)
{}
private:
int &a;
const int b;
Date d; ///这三个必须通过初始化列表来初始化
};
void main()
{
test t(100,200,300);
}
注:其次在构造函数前加explicit,可以防止隐式转换
class Test
{
public:
Test(int data = 0) :m_data(data)
{}
private:
int m_data;
};
int main()
{
Test t;
t = 10;若是光看这一行代码,会认为t是int类型,实际上t是Test类型,这就是隐式转换。为了避免这种歧义,可以加上explicit防止其隐式转换。
return 0;
}
class Test
{
public:
explicit Test(int data = 0) :m_data(data)
{}
private:
int m_data;
};
int main()
{
Test t;
t = 10;这样就编译不通过了,要想编译通过可以强转。
t = (Test)10;
return 0;
}
class Date
{
public:
Date(int year) :_year(year)//可以编译通过
{}
explicit Date(int year) :_year(year)//不能编译通过
{}
Date()
{
}
Date&operator=(const Date&d)
{
if (this != &d)
{
_year = d._year;
}
return *this;
}
private:
int _year;
};
void main()
{
Date d(10);
d = 2019;//,看起来这句代码是int类型给Date类型赋值,本质上代码是用2019初始化一个对象,然后对象给对象赋值,进行操作符重载,这就是隐式转换,加上explicit可以避免这种隐式转换
}
②:析构函数:对对象做最后的处理(不是销毁)
1.函数名为类名前加取反符号~
2.无参无返回值
3.不能重载
4.由于是在栈上创建的对象,所以先定义的对象后调用析构函数,后定义的对象先调用构造函数
class test
{
public:
~test()//析构函数
{
strcpy(name,"zhangsan");
strcpy(sex, "nan");
age = 15;
}
private:
char name[20];
char sex[5];
int age;
};
void main()
{
test t1;//后调用析构函数
test t2;//先调用析构函数
}
③:拷贝构造函数(构造函数的重载)
1.拷贝构造函数的参数只能有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
2.拿对象初始化对象时会调用拷贝构造函数,包括传值,拿对象做返回值
class test
{
public:
test(const test&t)//拷贝构造函数 test(test*const this,const test&t)
{
strcpy(name,t.name);
strcpy(sex,t.sex);
age = t.age;
}
private:
char name[20];
char sex[5];
int age;
};
void main()
{
test t1;
test t2 = t1;
test t3(t1);
}
注:
1.拷贝构造函数的参数可以不可以不要const?
答:可以,不过为了防止在拷贝构造函数内部将参数的值修改,还是加上const
2.拷贝构造函数的参数可不可以传值?
答:不可以,传值会引发无限递归,传值会一直初始化对象,就会一直调用拷贝构造函数,所以不行,只能使用传引用。
④.赋值运算符重载
1.函数名为operator后面接需要重载的运算符符号,其余和函数相同,可以有参数,可以有返回值
2. . .* sizeof ?: :: 这5个运算符不能重载
3.赋值运算符的四点注意:
a. 参数类型(传引用,加上const)
b. 返回值(引用返回)
c. 检测是否自己给自己赋值(if语句的判断)
d. 返回*this(return *this)
class test
{
public:
test&operator=(const test&t)//运算符重载 test&operator(test*const this,const test&t)
{
if(this!=&t)//防止自我赋值
{
strcpy(name,t.name);
strcpy(sex,t.sex);
age = t.age;
}
return *this;
}
private:
char name[20];
char sex[5];
int age;
};
void main()
{
test t1;
test t2;
t1 = t2;// 相当于t1.operator=(t2);operator=是函数名,t2是参数,t1的地址本来也是函数的参数,只不过这里没写。
}
注:
1.运算重载的函数参数可以不可以不要const?
答:可以不要,但为了防止在函数内部把参数的值改掉,所以还是加上const.
2.运算重载的函数参数可以不可以使用参数传值和用值返回?
答:可以,但是在参数传值和用值返回时,是要初始化对象的,所以会调用一次拷贝构造函数,而传引用是不会调用的。所以为了效率,最好还是用引用传参和引用返回。
3.运算重载可不可以不返回?
答:可以,但是不能连等了,例如上面的代码,如果不要返回值,就不能写成 t1 = t2 = t3 …这样的,但是t1 = t2还是可以的。因为连等的操作本质上这样的 t1.operator=(t2.operator=(t3)) ,如果没有返回值,这样那么t2.operateor这个函数就没参数,这样就无法正常调用。
⑤:const修饰的成员函数
1.在函数后加const是const修饰的成员函数,在函数后加const实际上修饰的是this指针,表明在该函数内部不能对this指针指向的内容进行修改
2.方法和常方法可以共存
class Data
{
public:
void show()//void show(Data*const this)
{
cout<<"year = "<<year<<endl;
}
void show()const//void show(const Data*const this)
{
cout<<"const year = "<<year<<endl;
}
private:
int m_year
};
void Test ()
{
Date d1 ;
d1.show(); //调void show()
const Date d2;
d2.show();//调void show()const
}
注意:
1.非const对象可以调用非const成员函数和const成员函数
2.const对象可以调用const成员函数,不能调用非const成员函数
3.非const成员函数可以调用其他非const成员函数和其他const成员函数
4.const成员函数可以调用其他const成员函数,不能调用其他非const成员函数
⑥:取地址即const取地址操作符重载
这两个一般不用写,系统会自动生成。
class Data
{
public:
Data* operator&()
{
return *this;
}
const Data* operator&()const
{
return this;
}
private:
int m_year;
int m_month;
int m_day;
};
5.静态(static)成员
由static修饰的成员是静态成员,由static修饰的成员函数是静态成员函数
注:1.静态成员的初始化必须在类外进行,并且初始化时不加static
2.静态成员函数没有this指针,不能访问非静态成员函数
3.类的静态成员函数不属于任何对象,所以不能通过对象来调用函数,但可以通过类名来调用类的静态函数
class Date
{
public:
static void fun()
{
cout<<"static fun "<<endl;
}
private:
static int _year;
};
int Date::_year = 10;//static成员函数初始化必须在类外进行
void main()
{
Date t(10);
Date::fun();//通过类名可以调用类的静态函数
}
注:1.非静态成员函数可以调用静态成员函数吗?反过来呢?
非静态成员函数可以调用静态成员函数,静态成员函数不能调用非静态成员函数
例:
class Date
{
public:
static void show1()
{
show2();//static修饰的show1不能调用show2()
}
void show2()
{
show1();//show2()可以调用static修饰的show1()
}
private:
static int _year;
};
int Date::_year = 10;
void main()
{
Date t;
t.show1();
t.show2();
}
6.友元
①:友元函数
友元函数是由friend来修饰的函数,在类里声明时,不受限制词的约束,在类外实现时,不加friend。
注:1.友元函数可以直接访问类的私有成员
2.友元函数不拿const修饰
3.一个函数可以是多个类的友元函数
class Date
{
friend ostream& operator<<(ostream& out, const Date&d);//标准输出流的友元函数
friend istream& operator>>(istream&in, const Date&d);//标准输入流的友元函数
public:
private:
static int _year;
};
int Date::_year = 10;
ostream& operator<<(ostream& out, const Date&d)
{
out << d._year;
return out;
}
istream& operator>>(istream&in, const Date&d)
{
in >> d._year;
return in;
}
void main()
{
Date t;
cin >> t;
cout << t << endl;
}
②:友元类
由static修饰的类为友元类,当一个类被修饰为友元时,它的所有成员函数都可能是另一个类的友元函数,都可以访问另一个类的非公有成员
即A是B的友元类,则A就可以访问B中的非公有成员
注:友元是单向的,不具有传递性。,即A是B的友元类,B是C的友元类,但A不是C的友元类。
class Date
{
friend class Time;//声明Time类是Date类的友元类,所以在Time里面可以访问Date的私有成员
public:
private:
static int a;
int b;
};
class Time
{
public:
void show()
{
cout << d.a << endl;
}
private:
static int _year;
int _month;
Date d;
};
int Time::_year = 10;
int Date::a = 10;
void main()
{
Time tm;
tm.show();
}
7.内部类
一个类定义在另一个类的内部,所以这个类就叫内部类,此时这个外部类对内部类没有任何的访问权限。
注:1.内部类就是外部类的友元类,而外部类不是内部类的友元类
2.sizeof(类) = sizeof(外部类),和内部类没有关系
3.内部类访问外部由static修饰的成员,枚举成员,可以直接访问,不需要通过外部对象来调,访问外部普通成员时,必须通过外部成员来调
此时Time类就是Date类的友元类
class Date
{
public:
class Time
{
public:
void show();
void fun(const Date&d)
{
a = 10;
cout << d.d << endl;可以通过外部成参数来访问外部的私有成员
}
private:
static int b;
int c;
};
private:
static int a;
int d;
};
int Date::a = 10;
int Date::Time::b = 20;//通过外部类和内部类的限制来初始化b
void Date::Time::show()//通过外部类和内部类的限制来实现方法
{
cout << Date::a << endl;
cout << Date::Time::b << endl;
}
void main()
{
Date d;//定义一个Date类
Date::Time t;//定义一个Time类,t可以访问d的成员,d不能访问t的成员,因为t是d的友元类
t.show();
}