类
class Circle
{
private:
double radius ; //成员变量
public : //类的访问控制
void Set_Radius( double r )
{
radius = r;
} //成员函数
double Get_Radius()
{
return radius;
}. //通过成员函数设置成员变量
double Get_Girth()
{
return 2 * 3.14f * radius;
} //通过成员函数获取成员变量
double Get_Area()
{
return 3.14f * radius * radius;
}
};
也可以这样:
class Date
{
public:
void init(Date &d);
void print(Date & d);
bool isLeapYear(Date & d);
private:
int year;
int month;
int day; };
void Date::init(Date &d)
{
cout<<"year,month,day:"<<endl;
cin>>d.year>>d.month>>d.day;
}
void Date::print(Date & d)
{
cout<<"year month day"<<endl;
cout<<d.year<<":"<<d.month<<":"<<d.day<<endl;
}
bool Date::isLeapYear(Date & d)
{
if((d.year%4==0 && d.year%100 != 0) || d.year%400 == 0)
return true;
else
return false;
}
封装
封装有2层含义(把属性和方法进行封装对属性和方法进行访问控制)
Public修饰成员变量和成员函数可以在类的内部和类的外部被访问。
Private修饰成员变量和成员函数只能在类的内部被访问。
Protected保护控制权限,在类的继承中跟private有区别,在单个类中和private是一样的。
构造与析构
class Test {
public:
Test() { // 无参数构造函数
;
}
Test(int a, int b) { //带参数的构造函数
;
}
Test(const Test &obj) { //拷贝构造函数
;
}
~Test(){. //析构函数
;
}
private:
int a;
int b;
};
构造函数和析构函数都不能有返回值,且析构函数不能有参数。
析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作。
即:
构造规则:
1 在对象创建时自动调用,完成初始化相关工作。
2 无返回值,与类名同,默认无参,可以重载,可默认参数。
3 一经实现,默认不复存在。
析构规则:
1 对象销毁时,自动调用。完成销毁的善后工作。
2 无返值 ,与类名同。无参。不可以重载与默认参数
3.析构函数调用顺序,谁先构造的,谁就后析构
若没写构造函数和析构函数,编译器会提供默认的构造函数和析构函数,通常函数里面什么都没有
拷贝构造函数
class Test {
public:
Test() {
cout<<"我是无参构造函数,被调 了"<<endl;
}
Test(int a) {
m_a = a;
}
Test(const Test &another_obj) //拷贝构造函数 {
cout<<"我也是构造函数,我是通过另外一个对象, 来初始化我 "<<endl;
m_a = another_obj.m_a;
}
~Test() {
cout<<"我是析构函数, 动被调 了"<<endl;
}
void printT()
{
cout << "m_a = " << m_a <<endl;
}
void operator=(const Test &another_obj){ //重载操作符
m_a = another_obj.m_a;
}
private:
int m_a;
};
int main(){
Test t1(3); //构造函数是初始化时调用
Test t2(t1); //调用了拷贝构造函数
Test t3=t1; //调用了拷贝构造函数
Test t4; //没有调用了拷贝构造函数,因为是无参,调用的是无参构造函数
t4=t1; //重载操作符,可以达到拷贝构造函数的效果,但无调用拷贝构造函数
}
//这里是t1最后析构
有关拷贝构造的应用场景和匿名对象
#include <iostream>
using namespace std;
class Location
{
//带参数的构造函数
public:
Location( int xx = 0 , int yy = 0 ) {
X = xx ;
Y = yy ;
cout << "Constructor Object." <<endl;
}
Location(const Location & obj) //copy构造函数
{
X = obj.X;
Y = obj.Y;
cout <<"Copy Constructor." <<endl;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () {
return X;
}
int GetY () {
return Y;
}
private :
int X;
int Y;
};
//结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调匿名 对象类的copy构造函数)
//
//结论2: 有关匿名对象的去和留
//如果 匿名对象初始化给另外一个同类型的对象, 匿名对象转成有名对象
//如果 匿名对象赋值给另外一个同类型的对象, 匿名对象被析构
Location g()
{
Location temp(1, 2);
return temp;
} //匿名的对象=temp 匿名对象. 拷贝构造(temp),然后temp析构
void test1()
{
g();
}
void test2()
{
// 匿名对象初始化m 此时c++编译器 直接把匿名对转成m; (扶正) 从匿名转成有名字了m
//就是将这个匿名对象起了名字m,他们都是同一个对象
Location m = g(); //匿名对象,被扶正,不会析构掉
cout<<m.GetX()<<endl;;
}
void test3()
{
// 匿名对象 赋值给 m2后, 匿名对象被析构
Location m2(1, 2);
m2 = g(); //因为匿名对象=给m2, 匿名对象,被析构
cout<<m2.GetX()<<endl;;
}
int main(void)
{
test1();
test2();
test3();
return 0;
}
浅拷贝与深拷贝
默认拷贝构造函数
很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了
ps:定义在类里的成员变量 string a:内部会帮你做好深拷贝
#include <iostream>
#include <assert.h>
using namespace std;
class Test {
public:
Test() {
cout<<"我是无参构造函数,被调了"<<endl;
p=new int(10);
}
Test(int a) {
m_a = a;
}
~Test() {
cout<<"我是析构函数, 被调了"<<endl;
assert(p!=NULL);
delete p; //这里会出现异常,因为t2和t1指向同一个堆,一个内存区不能同时delete两次。
}
void printT()
{
cout << "m_a = " << m_a <<endl;
}
private:
int m_a;
int *p;
};
int main(){
Test t1;
Test t2(t1);
}
使用默认拷贝构造函数都是浅拷贝。 有时候浅拷贝不会发生异常,当涉及动态变量时就可能发生异常。
解决方案: 手工编写拷构造函数 使用深copy
如 在拷贝函数中用new 或 malloc开辟新的内存区来赋值。
这个帖子里,大佬写得很好(https://www.cnblogs.com/alantu2018/p/8459250.html)
构造函数的初始化列表
#include <iostream>
using namespace std;
class ABC
{
public:
ABC(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
printf("a:%d,b:%d,c:%d \n", a, b, c);
printf("ABC construct ..\n");
}
~ABC() {
printf("a:%d,b:%d,c:%d \n", a, b, c);
printf("~ABC() ..\n");
}
private:
int a;
int b;
int c;
};
class MyD
{
public:
MyD():abc1(1,2,3),abc2(4,5,6),m(100) //构造函数的初始化列表,冒号后面是直接初始化,调用构造函数
{
cout<<"MyD()"<<endl;
}
~MyD() {
cout<<"~MyD()"<<endl;
}
private:
ABC abc1;
ABC abc2;
const int m;
};
int main()
{
MyD myD;
return 0;
}
构造顺序与初始化列表顺序无关,按构造对象成员的顺序
#######构造中调用构造是危险的行为
#include <iostream>
using namespace std;
//构造中调用构造是危险的行为
class MyTest
{
public:
MyTest(int a, int b, int c)
{
_a = a;
_b = b;
_c = c;
}
MyTest(int a, int b)
{
_a = a;
_b = b;
MyTest(a, b, 100); //产生新的匿名对象 ,之后会被析构
}
~MyTest() {
printf("MyTest~:%d, %d, %d\n", _a, _b, _c);
}
int getC()
{
return _c;
}
void setC(int val)
{
_c = val;
}
private:
int _a;
int _b;
int _c;
};
int main()
{
MyTest t1(1, 2);
printf("c:%d\n", t1.getC()); //请问c的值是? 答案是乱码
return 0;
}
new和delete
new int;
//开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);
//开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];
//开辟一个存放字符数组(包括10个元素)的空间,返回元素的地址
new int[5][4];
//开辟一个存放 维整型数组( 为5*4)的空间,返回元素的地址
float *p=new float (3.14159);
//开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
new运算符动态分配堆内存
使用形式: 指针变量=new 类型(常量);
指针变量=new类型[表达式];
作用:从堆分配一块"类型"大小的存储空间,返回首地址
其中:“常量”是初始化值,可缺省
创建数组对象时,不能为对象指定初始值
delete运算符释放已分配的内存空间
使用形式: delete 指针变量;
delete[] 指针变量;
其中:“指针变量”必须是一个new返回的指针
用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分 配空间是否成功。
malloc不会调用类的构造函数,而new会调用类的构造函数 !!!!!
Free不会调用类的析构函数,而delete会调用类的析构函数!!!!
静态成员变量和成员函数
静态成员变量
//声明
static 数据类型 成员变量; //在类的内部
//初始化
数据类型 类名::静态数据成员 = 初值; //在类的外部
//调用
类名::静态数据成员
类对象.静态数据成员
1,static 成员变量实现了同类对象间信息共享。
2,static 成员类外存储,求类大小,并不包含在内。
3,static 成员是命名空间属于类的全局变量,存储在 data 区。
4,static 成员只能类外初始化。 初始化后想改的话要通过静态成员函数
5,可以通过类名访问(无对象生成时亦可),也可以通过对象访问。
#include <iostream>
using namespace std;
class Box
{
public:
Box(int l, int w):length(l),width(w) {
}
int volume()
{
return length * width * height;
}
static int height;
int length;
int width;
};
int Box::height = 5;
int main() {
cout<<Box::height<<endl;
Box b(1,1);
cout<<b.height<<endl;
cout<<b.volume()<<endl;
return 0;
}
静态成员函数
//声明
static 函数声明
//调用
类名::函数调
类对象.函数调
1,静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员, 完 成对静态数据成员的封装。
2,静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时this 指 针被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。
#include <iostream>
using namespace std;
class Student
{
public:
Student(int n,int a,float s):num(n),age(a),score(s){}
void total()
{
count++;
sum += score;
}
static float average();
private:
int num;
int age;
float score;
static float sum;
static int count;
};
float Student::sum = 0;
int Student::count = 0;
float Student::average() { //函数可以在类里面定义,但是不能访问类的对象內的成员,因为this只能用于非静态成员函数内部
return sum/count;
}
int main() {
Student stu[3]= {
Student(1001,14,70),
Student(1002,15,34),
Student(1003,16,90)
};
for(int i=0; i<3; i++) {
stu[i].total();
}
cout<<Student::average()<<endl;
return 0;
}
static占用的大小
只有类中的普通成员变量算进类的大小,静态成员变量和函数不算进去。
class C1 {
public:
int i; //4
int j; //4
int k; //4
}; //12
class C2 {
public:
int i;
int j;
int k;
static int m; //4,但不算进去
public:
int getK() const { return k; } //4,但不算进去
void setK(int val) { k = val; } //4,但不算进去
};//12
C++类对象中的成员变量和成员函数是分开存储的
成员变量: 普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对
齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。
以下很重要
1、C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模 型仍然有效!
2、C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
3、静态成员函数、成员变量属于类
4、静态成员函数与普通成员函数的区别 静态成员函数不包含指向具体对象的指针 普通成员函数包含一个指向具体对象的指针
This
#include <iostream>
using namespace std;
class Test {
public:
Test(int a, int b) //---> Test(Test *this, int a, int b)
{
this->a = a; //this是一个常指针Test* const this,只指向对象本身,本能再指向其它
this-> b = b; }
void printT()
{
cout<<"a: " <<a <<endl;
cout<< "b: " << this->b <<endl;
}
protected:
private:
int a;
int b;
};
int main(void)
{
Test t1(1, 2); //===> Test(&t1, 1, 2);
t1.printT(); // ===> printT(&t1)
return 0;
}
(1)若类成员函数的形参和类的属性,名字相同,通过this指针来解决。
(2)类的成员函数可通过const修饰
全局函数和成员函数
Test& add(Test &t2) //*this //函数返回引用
{
this->a = this->a + t2.getA();
this->b = this->b + t2.getB();
return *this; //*操作让this指针回到元素状态 //如果想返回一个对象的本身,在成员方法中,用*this返回
}
Test add2(Test &t2) //*this //函数返回元素
{
//t3是局部变量
Test t3(this->a+t2.getA(), this->b + t2.getB()) ;
return t3;
}
如果想对一个对象连续调用成员方法,每次都会改变对象本身,成员方法需要返回引用。
如:T1.add(t2).add(t2)