C++基础
类与对象
将数据放在类的私有部分叫作数据隐藏。 类定义可以放在头文件中。
C++中默认访问是私有的,因此在类定义中不必使用关键字private。
class Wr //Wr类的定义
{
float mass;
char name[20];
public:
void tellall(); //类中的方法声明
Wr(float mass,char name[]); //构造函数
~Wr(); //析构函数
………………………………
};
//对Wr类中的普通成员函数tellall()进行定义,
//tellall函数里可直接调用Wr中的私有属性和方法。
void Wr::tellall()
{
}
构造函数
在进行对象初始化时会自动调用的函数就叫构造函数。
特点:
无返回值,void也不行,直接不写。
方法名就写类名。
构造重载:在有非默认的构造函数(即用户自定义的)的情况下,可以利用重载定义另一个构造函数-----一个没有参数的构造函数。
构造函数的定义:
Wr::Wr(float mass,int name)
{
}
构造函数的初始化列表
Wr::Wr(float mass,int name):this.mass(mass),this.name(name)
{
}
委派构造函数
当某一个类或者结构体含有多个构造函数,并且构造函数里面有许多相同的代码,那么可以把相同的代码抽取出来,写成一个通用的构造函数,其他构造函数通过委派上述通用的构造函数,达到减少代码书写量的目的。
例子:
class C {
C(int a_) {
work1();
work2();
work3();
a = a_;
}
C(int a_, double b_) {
work1();
work2();
work3();
b = b_;
}
C(int a_, char c_) {
work1();
work2();
work3();
c = c_;
}
private:
int a;
double b;
char c;
void work1();
void work2();
void work3();
};
上述类C拥有三个构造函数,但是它们都执行相同的一部分代码——work1、work2和work3。可以通过书写一个通用的构造函数,替换掉多余重复的代码。其中通用的构造函数叫做目标函数,委派目标函数的构造函数称为委派构造函数。
class C {
public:
C() { //目标函数
work1();
work2();
work3();
}
C(int a_) :C() { //委派构造函数
a = a_;
}
C(int a_, double b_):C() { //委派构造函数
b = b_;
}
C(int a_, char c_) :C() { //委派构造函数
c = c_;
}
private:
int a;
double b;
char c;
void work1() {
printf("do work1\n");
}
void work2() {
printf("do work2\n");
}
void work3() {
printf("do work3\n");
}
};
析构函数
当对象过期(使用完)时,程序会自动调用一个成员函数-----析构函数,析构函数会完成清理工作(收尾),如果析构函数使用new来分配内存,则析构函数将用delete来释放空间。
特点:无返回值,无参数,在类名前加上~即可
析构函数会在程序(代码块)执行完毕后才会自动执行。
析构函数的定义:
Wr::~Wr()
{
}
C++中的对象初始化
类名 变量名=(构造函数的参数列表);
int main()
{
Wr value=(6.15, "小明"); //对象初始化
return 0;
}
const成员函数
作用:使对象不会因为函数的调用而被修改
写法:需要在成员函数声明和定义的时候在函数头部的结尾加上 const 关键字。
形式:返回值 函数名(参数列表) const;
this指针:
形式: *this ----指调用方法的对象本身;this是对象的地址。
类的作用域:
直接访问:对象名.成员属性; 间接访问:对象指针变量->成员属性。
类只是描述了对象的形式,因此在对象创建之前,将没有用于存储的空间。
class Bakery
{
private:
1. ~~const int month=12;~~ //这条语句是错误的
double co[20];
};
如果要在类中定义一个常量:可以在类中定义一个枚举。将上面的1换为enum{month=12};
注意这里并未创建一个month的成员数据,month只是一个符号,编译器会将所有有这个符号的位置替换为12。(只需要提供一个符号常量,因此不需要变量)
C++中还有一种在类中定义常量的方法,即使用关键字static
将上面的1换为static const int month=12;
运算符重载:
若编译器发现dis2,sid,sara都是同一种类的对象,就可以编写这样的等式:
dis2=sid+sara;
此时,sid为调用对象,sara为参数。也可以使用运算符函数来替代上面的等式。
dis2=sid.operator + (sara); //运算符函数
二者是等价的。operator是关键字, .operator +(……)是用户自定义的类方法之一。
C++中函数的返回值不能是局部变量和临时对象的引用。
友元函数:
有一种非成员函数却能访问类的私有属性和方法,它们叫友元函数。
创建友元函数:
1. 将函数原型(声明)放在类声明中,并在原型前加上关键字friend。
2. 编写函数定义。因为其不是成员函数,因此不需要使用类名::函数名这样的限定。在定义中不需要用关键字friend。
class Bakery
{
double co[20];
public:
friend void KK();
Bakery(double co[]);
};
//该类中函数的定义
Bakery::Bakery(double co[])
{
}
void KK()
{
}
dis2=sid+2.34;<---> dis2=sid.operator+(2.34);
dis2=2.34+sid;----此时调用对象不是一个类,而是一个常量,要使编译器自动识别并转换,需要设一个友元函数 :
friend void operator +(double a, 类名 形参);
这样上面等式就不会报错了。
operator + 重载加号 ; operator / 重载除号 等。
类的自动转换:
若类定义中 :构造函数 Mr(double a); 则在主函数中可以有
Mr u; u=2.44;
这两句等价于 Mr u(2.44); //double常量2.44转换成了该对象的类型
这叫隐式转换,它是自动进行的,不用强制转换类型。
只有接受一个参数的构造函数才能作为转换函数(两个或两个以上参数的,需要将第一个参数后面的所有的其他参数设为默认值),才可以进行转换。
class Bakery
{
double co[20];
int value;
public:
Bakery(double co[],int value);
};
Bakery::Bakery(double co[],int value=10) {}
//为value设置默认值10
如果在声明前加上关键字explicit,则这种隐式转换特性会被关闭。
explicit Mr(double a);
当Mr(double a);中的数据为其他类型时,会被自动转换为double类型。此时要求构造函数不存在二义性-----即不存在两种只有一个参数的构造函数,编译器会不知道转换成哪一种。
转换函数
使转换函数可以使该类型的对象转换为其他类型。
写法: operator 类型名();
1. 转换函数必须是类方法之一。
2. 转换函数不能指定返回类型。
3. 转换函数不能有参数。
4. 必须返回转换后的值。
class Bakery
{
int value;
public:
Bakery(int value);
operator int();
};
Bakery::Bakery(int value) {this->value=10+value;}
Bakery::operator int() //转换函数,转换为int类型
{
return *this;
}
int main()
{
Bakery Tem;
int value1=100,value2=0;
Tem=value1; //value1由int 类型转换到了Bakery类型
value2=Tem; //Tem由Bakery转换为了int类型
return 0;
}
复制构造函数:
将一个对象复制到新创建的对象中,复制构造函数会逐个复制非静态成员的值(浅复制,值传递)
Mr(const Mr &) //复制构造函数
如果类成员有指针存在,则复制的是这整个指针所指向的地址。
深度复制:如果类中包含了使用new 初始化的指针成员,应自定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深复制。
如果复制的是整个指针,则同一个内容被两个不同的指针指向,当使用delete时,会释放指针所指的内容,此时另一个指针就成了空。
//浅复制
class Mr
{
double p;
double *str;
public:
Mr(const Mr &s);
};
Mr::Mr(const Mr &s)
{
this->p=s.p;
this->str=s.str; //这里相当直接复制的是整个指针所指的地址
//深度复制
//double temp=s.str; //获取指针所指地址上存放的内容
//this->str=temp;
};
所指内容是字符串,可以考虑用strcpy(str,s.str);
C++11中的空指针:*p=nullptr;
类的继承与多态
类的继承
class Rt : public Tab
{…………};
Rt的父类是Tab,且Tab是一个公有基类。
Rt被称为公有派生类,基类的公有成员将成为派生类的公有成员,可直接使用。派生类不能直接访问基类的私有成员。
class Tab //父类
{
int a;
int b;
double c;
public:
Tab(int &a,int&b,double &c); //构造函数
};
class Rt:public Tab
{
int d;
public:
Rt(int&d,int&a,int&b,double &c);
};
创建子类时,要同时为父类的属性赋值。
Rt::Rt(int &d,int&a,int&b, double &c):Tab(a,b,c)
{this->d=d;}
//将形参a,b,c作为实参传给Tab的构造函数,
//即以父类的方法构建a,b,c。
如果没有 :Tab(a,b,c) 则会自动使用默认构造函数,即 :Tab()
如果父类没有定义赋值构造函数,但又需要使用时,编译器会自动生成一个。(没有申请动态空间)
-
注意:创建派生类对象时,程序首先会调用基类构造函数,然后再调用派生类构造函数。基类构造函数用于初始化继承的数据成员,派生类的构造函数总是调用一个基类构造函数。派生类对象过期时,程序会优先调用派生类的析构函数,然后才会调用基类的析构函数。
-
上面
Rt(int &d,int&a,int&b,double&c):Tab(a,b,c)这个叫成员初始化列表,类只能将值传递回相邻的基类(只能用于构造函数)。 -
基类指针可以指向派生类对象,基类的引用也可以直接引用派生类对象,但是基类的指针或引用都只能用于调用基类属性和方法。(为多态奠定前提)
类的多态
在方法声明前加上关键字virtual,则程序会根据引用或指针指向的对象的类型来选择方法(即多态)。若没加上关键字virtual,则程序会根据引用类型或指针类型来选择方法,而不是根据对象类型来选择对应方法。
关键字virtual 只能用于类方法声明。
class Br //父类
{
private:
double value;
public:
Br(double a);
~Br();
virtual void Per(); //虚方法
virtual void Our(); //虚方法
};
class Bu : public Br //子类
{
private:
int a;
public:
Bu(int a, double value);
~Bu();
virtual void Per();
virtual void Our();
};
在基类中,将可以在派生类中重新定义的方法声明为虚方法。
- 在派生类方法中调用公有的基类的方法:
基类名::基类方法名();
- 友元函数不能是虚函数(virtual),只有类成员才能是虚函数。
- 注意:重新定义已继承的方法,应确保与原来的原型完全相同,如果返回值类型是基类引用或指针,则可以修改为指向派生类的引用或指针。------如果方法在基类中已被重载,则应该在派生类中重新定义所有版本。
访问控制protected:
protected 与 public,private相似。派生类可直接访问基类中的保护成员。对于外界来说,保护成员与私有成员类似,对于派生类内部来说,保护成员与公有成员相似。
抽象基类:
C++中通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0。
例子:
void ma(int a)=0; //类方法声明,纯虚函数
类声明中包含纯虚函数时,不能创建该类对象,只能作基类。
纯虚函数可定义也可不定义。、
C++静态成员与静态函数
静态成员函数归属 : 在 C++ 类中 , 静态成员函数 是一种 特殊的函数 , 该函数属于类 , 而不是属于 类的实例对象,静态成员同理。
- 即使 没有创建 类 的 实例对象 , 也可以 通过 类名:: 调用 类中定义的 静态成员函数以及静态成员 。
static void fun(); //静态成员函数
static int number; //静态成员
- 普通的成员在类中,进入main()函数时,函数会为它们提前分配空间(在栈上),然后运行到指定语句后,开始初始化对象,但静态成员不会提前分配空间,因此,需要在进入main()函数前,提前为静态成员分配空间(初始化)。
类型 类名::静态成员=初始化值。
int main()
{
return 0;
}
- 静态成员函数和静态成员的调用
//静态成员
类名::静态成员名; //方法1
对象名.静态成员名; //方法2
//静态成员变量
类名::静态成员函数名(); //方法1
对象名.静态成员函数名(); //方法2
例子:
#include <iostream>
using namespace std;
class Student
{
public:
// 带参构造函数
Student(int age, int height)
{
m_age = age;
m_height = height;
cout << "执行 Student 的构造函数" << endl;
}
~Student()
{
cout << "执行 Student 的析构函数" << endl;
}
static void fun() //静态成员函数
{ // 静态成员函数中访问非静态成员会报错
cout << "静态成员函数被调用 : number = "
<< number << endl;
}
public:
int m_age; // 年龄
int m_height; // 身高
static int number; // 在类内部定义静态成员
};
int Student::number = 1; // 在类外部初始化静态成员变量
int main() {
//静态成员变量
// 使用 域操作符 访问 类静态成员变量
// 类名::静态成员变量名
cout << "Student::number = " << Student::number << endl;
// 为 类 静态成员变量 赋值
Student::number = 2;
// 创建 Student 类型对象
Student s(10, 150);
// 使用 对象 访问 类静态成员变量
// 对象名称.静态成员变量名
cout << "s.number = " << s.number << endl;
// 静态成员函数
// 通过 类名:: 调用 静态成员函数
Student::fun();
// 通过 对象. 调用 静态成员函数
s.fun();
return 0;
}