文章目录
类和对象
类: 具有相同 属性 (变量)和 行为 (函数)的对象的集合。
类规范由两个部分组成:
+ 类声明:以数据成员的方式描述数据部分。(蓝图)
+ 类方法定义:描述如何实现类成员函数。(细节)
class CPeople
{
public : //不加public的话默认为private
int a ;
void fan ( )
{
cout << "han" << endl ;
}
} ; //内定义的花括号后面也会有一个;像结构体一样。在C++中结构体是一个特殊的类。
int main ( )
{
//声明一个类的对象
CPeople op; //声明一个栈区变量
op.a=12;
op.fan();
//声明指针对象是需要new申请空间
CPeople* ep = new CPeople; //申请一个堆区变量
delete ep;
return 0;
}
- 内定义的花括号后面也会有一个;像结构体一样。在C++中结构体是一个特殊的类。
访问修饰符
- public:共有的,都可以使用
- private:纯私有,类内可见
- protected:受保护的,类内、子类可见
struct和class创建类的区别
- class创建类,不添加访问修饰符,默认为私有成员
- struct创建类,不添加访问修饰符,默认为公有成员
构造函数
- 只有静态、常量、整形(static const int)的数据成员才能在类中初始化。但是只有初始化后的数据成员才能被使用。
- 构造函数的作用就是为数据成员初始化。
class Cstu
{
int age;
float f;
Cstu() //构造函数,名字与类名相同
{
age=12;
f=12.12;
}
};
构造函数是在main()函数中创建类对象的时候自动调用。
构造函数的参数传递
int main()
{
CStu stu(12); //构造函数的参数传递
CStu* stu1 = new CStu(13,12m5f); //栈区 指针的定义
cout << stu.age << endl;
}
多个构造函数为函数的重载,根据调用参数来选择调用那个函数
当我们不写构造函数时,系统会自动调用一个参数和函数体都为空的构造函数,当我们一旦自己创建了一个构造函数时,系统默认的构造函数将会被删除。系统默认的构造函数与我们自己创建的构造函数为覆盖的关系,而不是重载的关系。
构造函数可以在类内声明类外定义。
class Cstu
{
int age;
float f;
Cstu(int age=12,float f=13,6) //构造函数,名字与类名相同
{
age=12;
f=12.12;
}
};
//类内声明内外定义的时候,在类外定义的构造函数前面要加上类名作用域Cstu::这样才知道该构造函数是哪个类的。
Cstu::Cstu(int age=12,float f=13,6)
{
age=a;
f=b;
};
析构函数
对象在释放的时候自动创建的一个函数。
class CStu
{
int* pp;
public:
int age;
CStu() //构造函数
{
cout << "构造函数" <<endl;
pp =new int(10);
}
~CStu() //析构函数
{
cout << "析构函数" <<endl;
delete pp;
}
};
class person
{
// 构造函数
person()
{
cout<<"无参构造函数"<<endl;
}
person(int a)
{
cout<<"有参构造函数"<<endl;
}
// 析构函数
~person()
{
cout<<"析构函数"<<endl;
}
};
void text()
{
person p;
// 不能写成person p(),这样会被当成一个函数的声明
}
int main(void)
{
text();
// 这个函数调用后会自动调用 p的构造函数和析构函数,
// 在该函数开始调用的时候调用构该函数
// 在该函数调用完退出时调用析构函数
// 会输出: 构造函数
// 析构函数
person b;
// 这个函数调用后会自动调用构造函数
// 会输出: 构造函数
// 在按任意键推出结束程序的时候才会有析构函数调用,但是那个时候控制台关闭了
system("pasue");
}
- 注意:调用默认构造函数的时候需要不要加()不然会认为是定义了一个函数
拷贝构造
形式:给构造函数传递一个类的引用
CStu(const CStu& a)
{
}
什么时候会用到靠背构造
- 当新定义一个对象,并把另一个对象作为新对象的参数的时候
CStu s1;
Cstu s1new(s1); // 这个时候会直接调用调用拷贝构造
- 现有对象给新的对象赋值
CStu s1;
Cstu s1new = s1 ; // 这个时候会直接调用调用拷贝构造
- 通过一个临时对象给新的对象初始化
CStu s1;
Cstu s1new = Cstu(s1) ; // 这个时候会直接调用调用拷贝构造
拷贝构造的功能
默认的拷贝构造函数, 逐个的复制非静态成员的值,复制的是成员的值。
// 拷贝构造
person(const person &p) //参数要是同名的
}
int main()
{
// 拷贝构造函数的调用
person p1;
person p2(p1); // 调用拷贝构造,参数是一个对象
// p2的值都拷贝自p1
}
括号发、显式法、隐式发
//括号法
person p;
person p0(10);
person p1(p);
// 显式法
person p2=person(10); // 显示法有参构造
person p3=person(p2); // 显示法拷贝构造
// 隐式法
person p4=10; // 就相当于 person p4 = person(10);
person p5 = p1; // 隐式拷贝构造
构造函数的三种使用时机
//
void DoWork1(person p)
{
person p1(10);
person p2(p1);
}
void
//二、 值传递的方式给函数传参
void DoWork2(person p)
{
}
// 当一个函数的参数为一个类对象的时候,调用该函数也会运行该类的构造函数和析构函数
//三、 值参数返回类对象
person DoWork3()
{
person p1;
return p1;
}
// 调用时也会调用类的 构造函数和析构函数
- 如果我们写了有参构造函数,那么编辑器就不会创建默认构造,但是依然会创建默认拷贝构造
- 如果我们写了拷贝构造函数,那么编辑器就不会创建其他的构造函数(默认构造函数)
浅拷贝和深拷贝
- 浅拷贝:简单的拷贝赋值操作
- 深拷贝:在堆区从新申请空间进行拷贝构造
浅拷贝
- 浅拷贝会有一个问题:会出现重复释放内存空间而出错的问题
class person
{
public:
int m_age;
int m_height;
person(int age,int height)
{
m_age = age;
m_height = new int(height);
cout<<"构造函数"<<endl;
}
~person()
{
// 将堆区代码释放
if(m_height!=NULL)
{
delete m_height;
m_height=NULL;
}
}
void doWork()
{
person p1(20,180);
// 拷贝构造
person p2(p1);
}
int main()
{
doWork();
// 在调用的时候会出错。
}
}
- 主函数调用doWork()函数时会出错原因
- 在调用的时候会出错。
- p1申请的height空间,拷贝构造也会拷贝同样的,空间地址也一样
- 堆区释放先进后出,在程序结束时,先调用拷贝构造p2的析后函数把释放了他自己height空间,然后p1在调用析构函数的时候,就会出现重复释放内存空间的情况。
深拷贝构造
- 解决浅拷贝构造的方法是深拷贝构造,自己创建一个一个拷贝函数,而不是使用系统为你创建的拷贝构造
person(const person &p)
{
m_Age = p.m_age;
m_height = new int(*p.m_height);
}
- 总结:如果有在堆区开辟内存空间的的操作,一定要自己提供拷贝构造函数(深拷贝),这样可以防止拷贝到来的问 题
类对象作为类成员
- 当一个a类中定义了一个其他b类的对象时,a类会先构造b类的构造函数,在构造b类的构造函数(构造函数先他人,后自身)。
- 析构函数与构造函数相反(先自身,后他人 )
静态成员函数
- 静态成员函数只能访问静态成员变量(static只能访问static),不能访问非静态成员变量
class person
{
public:
static int a;
int b;
static void fun()
{
a=100; // 可以访问
b=200; // 不可以
}
};
this
- 成员变量和成员函数时分开储存的。
- 非静态成员变量所占内存空间在对象类对象空间中
- 非静态成员函数和静态成员变量所占空间不在类的对象的空间中
用途:
- 形参和实参同名的时候,用this指针来区分,
- 在类的非静态成员函数中返回对象本身用return *this
this是一个指针,是当前类的一个指针(类型为例如Cas*[),区分类中的变量还是构造函数中的局变量
class CStu
{
public:
int a;
CStu(int a)
{
this->a = a; // this->a这个a就表示第四行中的int a的a,是CStu类本身的a
}
};
this指针不是成员,是类成员函数的隐含参数
空指针访问成员函数
- 空指针不能访问成员,否则会出错,为避免报错,可以添加一个空指针判断
因为到函数在调用成员属性的时候,每个成员属性在他的前面都默认加了一个this(this->age),告诉你这是当前对象的一个属性,但是p是一个空指针。
clacc person
{
public:
int age
void show_1()
{
cout << "this is person class" << endl;
}
void show_2()
{
cout << "age=" << age << endl;
}
}
void text()
{
person *p = NULL; // 一个person类的空指针
p->show_1(); //调用函数1的时候会正常调用
p->show_2(); //会出现错误
// 因为到函数2在调用成员属性的时候,每个成员属性在他的前面都默认加了一个this(this->age),告诉你这是当前对象的一个属性,但是p是一个空指针。
// 函数1没有指针的问题,只是一个输出函数。
}
// 避免出现错误
void show_2()
{
//添加一个空指针判断
if(this == NULL)
{
return;
}
cout << "age=" << age << endl;
}
初始化列表
初始化列表:给数据成员进行初始化。
构造函数:给数据成员进行赋值。
使用初始化列表的四种情况
- 初始化成员是对象
- 常量(const)成员变量
- 子类初始化父类成员
- 类成员为引用(&)类型的时候
- 初始化和赋值的区别
int a = 12 ; // 初始化
int b ;
b = 4 ; // 赋值
初始化列表的写法:在构造函数的()后面+:变量(值)
CStu():a(12),f(12.3f) //这样给初始化为12,f初始化为12.3
{
};
初始化列表中初始化的顺序,由变量被定义的顺序决定,不由初始化列表中的顺序决定。
CStu(int c) : a(c)
{
}
c是一个局部变量,用c给赋值,它的作用范围就是这个构造函数,出了这个函数,它的值就会被回收,当出了这个范围之后,再次调用a的时候值就不确定了
常对象 const
- 常对象必须在定义对象的时候指定是常对象
A const a; // a是一个常对象
常对象中的数据成员都是常变量,且都要有初始化值
- 常对象只能调用该对象的const类型的函数
常函数
形式:在函数名后面的()后面+const
class CStu
{
public:
void show() const //常函数
{
}
};
常函数中不能修改类中的数据成员,但是可以使用,在常函数中自己定义的变量是可以修改的
- 常对象不可以访问非常成员函数
- 非常对象可以访问常成员函数
- 常成员函数只能访问常规对象中的数据成员,不能修改常对象中的数据成员
常对象(const CStu st;)不能调用常函数,但是可以调用普通函数
class CStu
{
public:
int fun()
{}
void show() const //常函数
{}
};
int main()
{
const CStu st; //常对象
st.show() //不可以,常对象不能调用常函数
st.fun() //可以,常对象能调用普通函数
}
常指针
- 常指针
int* const p ;
-
巧记:const离得近,钉住了指针
指着所指向的指向的位置不能变化,赋值后不能改变指向别处;但是指向对象,该对象的值可以改变。 -
指向常量的指针
const int* p;
- 巧计:const离得远,钉不住指针
指针所指向的对象,该对象是一个const类型的量, 该对象的值不能改变,但是指针的指向可以改变
静态类
静态成员 static
class CStu
{
public:
static int a; // 静态成员类外初始化
CStu()
{
}
};
int CStu::A=12; // 静态成员类外初始化
只有静态 (static) 常量( const) 整形(int,char,long,short)成员才能在类中初始化
- 静态成员函数不能调用类中的数据成员和成员函数,可以调用静态成员。(静态成员函数只能使用静态成员变量。)
class CStu
{
public:
int a;
static void fun()
{
cout << "static menber";
a=12; //不行,静态成员函数不能调用类中的数据成员和成员函数
}
};
- 静态成员函数没有this指针。
this指针,是在main()函数中创建类成员的时候才会有this指针。而静态成员可直接有类名作用域(CStu::a=12)调用。这就说明:静态成员和对象并没有直接的关系,它是我们类本身的一个属性,只要类存在,静态成员就会存在,他在内存中就是有空间的。
友元
在一个类中定义一个友元函数,在友元函数就可以调用该类的私有成员。
- 定义方法是在函数前面+friend:friend void fun()
class Cstd
{
private:
int age;
void fun()
{
age=12;
cout<<age<<endl;
}
friend void chazhao(); //把chazhao()函数声明生Cstd的一个友元,他就可以调用他的私有成员。
};
void chazhao()
{
Cstd stu;
stu.fun(); // chazhao函数就可以调用这些私有成员了
}
访问修饰符为类提供了封闭性,但是友元打破了这种封闭性。
友元类
class Building
{
// 创建了一个友元类,GoodGay这个类是本类的友元,可以访问私有成员
friend class GoodGay;
public:
string SittingRoom
private:
string BadRoom ;
public:
Building()
{
SittingRoom = "客厅";
BadRoom = "卧室";
}
};
class GoodGay
{
GoodGay()
{
g_Building = new Builidng;
}
void visit()
{
cout << "好基友正在访问:" << g_Building->SittingRoom << endl;
// SittingRoom是public类,可以访问
cout << "好基友正在访问:" << g_Building->BadRoom << endl;
// BadRoom是private类,由于GoodGay是友元类,所以可以访问
}
};
成员函数做友元
class Building
{
// 告诉编译器,GoodGay类下的visit()函数是本类的友元,可以访问私有成员
friend void GoodGay::visit_1();
public:
string SittingRoom
private:
string BadRoom ;
public:
Building()
{
SittingRoom = "客厅";
BadRoom = "卧室";
}
};
class GoodGay
{
void visit_1()
{
}
void visit_2()
{
}
};
内联函数
内联函数类似于C语言中的宏定义,在函数调用的时候,直接把函数体替换内联函数的定义来执行函数
- 关键词是:inline
inline void fun() //一个内联函数
{
}
inline double square(double x) //一个内联函数
{
return x*x;
}
int main()
{
double a;
a=square(5.0);
}
内联函数相较于常规函数,占用的内存稍大,效率高。
运算符重载
应用中对象进行相加时,由于不知道对象中的哪个成员需要进行相加,然后对加号进行重新的定义,使得对象可以进行运算。加法乘法等等运算符都可以进行重做。
- 运算符重载有两种:全局函数重载、成员函数重载
- 全局函数重载:定义在类外
- 成员函数重载:定义在类内
全局函数重载
- 关键词:operator
void operator + (DStu& st,int a) //+:对+进行重载,传递参数时最好传递对象的引用。
{
}
class CStu
{
public:
int nage;
double sScare;
};
// 全局函数重载
void operator+(CStu& st,int a)
{
cout <<(st.nage + a) << endl; //表示该加法重载,使类的nage成员+一个int类型常数
}
void operator+(int a , CStu& st)
{
cout <<(st.nage + a) << endl;
}
// 调用的时候根据参数的顺序,调用不同的函数。
int main()
{
CStu st1,12;
st1 + a2; //根据参数的类型和顺序会调用第1个
3 + st2; //会调用第2个
}
成员函数重载
class Person
{
int a;
int b;
// 成员函数重载
Person operator + (Person &p)
{
Person temp;
temp.a = this->a + p.a ;
temp.b = this->b + p.b ;
return temp
}
};
void test()
{
Person p1;
p1.a=10;
p1.b=11;
Person p2;
p1.a=12;
p1.b=13;
Person p3;
p3 = p1 + p2;
}
注意
- 对于内置的数据类型(int double等)的表达式的运算符是不可以改变的
- 不要滥用运算符重载
其它运算符重载
<< 运算符重载
- p是一个类,cout << p 就可以直接输出类中的所有成员变量
- 只能用全局函数重载<<运算符
// cout的数据类型是ostream(输出流)
ostream &operator << (ostream &cout , person &p)
{
cout << "a =" << p.a << "b=" << p.b << endl;
return cout;
}
- 如果成员变量为私有的成员,可以把重载运算符设置为友元
class person
{
friend ostream &operator << (ostream &cout , person &p);
private:
int a;
int b;
};