第一层含义:封装是面向对象程序设计最基本的特性。把数据(属性)和函数(方法)合成一个整体,这在计算机世界中是用类和对象实现的。(把属性和方法进行封装)
第二层含义:把客观事物封装成抽象的类,并且类可以把自己的属性和方法只让可信的类或者对象操作,对不可信的进行信息的隐藏。(对属性和方法进行访问控制)
在C++中可以对类的属性和方法定义访问级别,public修饰的属性和方法,可以在类的内部访问,也可以在类的外部进行访问。private修饰的属性和方法,只能在类的内部进行访问。
类是把属性和方法进行封装,同时对类的属性和方法进行访问控制。
类是由我们根据客观事物抽象而成,形成一类事物,然后用类去定义对象,形成这类事物的具体个体。
类是一个数据类型,类是抽象的,而对象是一个具体的变量,是占用内存空间的。
当我们创建一个对象的时候,常常需要做某些初始化操作,例如属性进行赋初值。为了解决这个问题,C++编译器提供了构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在创建对象时自动执行。
定义:C++类中可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数。构造函数可以有参数列表,但是不能有函数返回值。
调用:一般情况下C++编译器会自动调用构造函数,但在一些情况下则需要手工调用构造方法。
拷贝构造函数的调用方式
# define _CRT_SECURE_NO_WARNINGS
# include
using namespace std;
class Student
{
private:
char * name;
int age;
public:
Student(char * name, int age)
{
/* 在构造函数中分配堆内存 */
this->name = (char *)malloc(sizeof(name)+1);
/* 初始化成员变量 */
strcpy(this->name, name);
this->age = age;
cout << "构造函数被执行..." << endl;
}
~Student()
{
if (this->name != NULL)
{
/* 在析构函数中释放堆内存 */
free(this->name);
this->name = NULL;
this->age = 0;
}
cout << "析构函数被执行..." << endl;
}
void print()
{
cout << this->name << " = " << this->age << endl;
}
};
int main()
{
Student stu("王刚", 21);
stu.print();
return 0;
}
a.拷贝构造函数的执行原理
拷贝构造函数有四种调用方式,当我们不写拷贝构造函数的时候,C++默认的帮我们写了拷贝构造函数,其函数体实现的是简单的成员变量的值的拷贝。这种默认的拷贝方式,我们称为浅拷贝,即只是简单的复制成员变量的值到另一个对象中。
b.深拷贝问题的抛出
当我们的成员变量是动态分配的一组堆内存的时候,这个时候C++默认的为我们写的拷贝构造函数把成员变量的值进行了复制,那么副本也会指向原始对象所分配的堆内存,当我们在析构函数中释放堆内存的时候,就会发生两次析构现象(一次是原始对象释放堆内存,一次是副本对象释放堆内存)因此造成程序崩溃。
当类中没有定义任何构造函数的时候,C++编译器默认为我们定义一个无参构造函数,函数体为空。
当在类中定义任意构造函数后(包括拷贝构造函数),C++编译器就不会为我们定义默认构造函数,也就是说我们定义了构造函数就必须要使用。
C++默认提供拷贝构造函数是指向的浅拷贝。只是进行了简单的成员变量的值的复制。
拷贝构造函数只有当我们写了以后C++编译器才不会给我提供,如若不写则C++编译器会一直提供默认的拷贝构造函数。
通常我们创建的对象都是由C++编译器为我们在栈内存中创建的,我们无法对其进行生命周期的管理。所以我们需要动态的去建立该对象,因此我们需要在堆内存中创建对象和释放对象。在C语言中为我们提供了malloc()函数和free()函数来为我们提供在堆内存中分配变量的方式,但是在C++中引入了new和delete关键字来让我们动态的创建和释放变量。
new关键字是用来在堆内存中创建变量的,格式为:Type * ptr = new Type(常量/表达式); 其参数列表中的常量/表达式可以用来给变量初始化,也可以省略不写。其返回结果为该类型的指针。如果内存分配失败则返回空指针。
delete关键字是用来释放用new关键字创建的内存,格式为delete ptr(释放数组必须需要加中括号,delete [] ptr)。
new关键字在分配内存的时候,会根据其创建的参数调用相应的类的构造函数。delete关键字会在释放内存之前,会首先调用类的析构函数释放对象中定义的内存。
malloc和free关键字不会去调用类的构造函数和析构函数。
通常我们创建的对象都是由C++编译器为我们在栈内存中创建的,我们无法对其进行生命周期的管理。所以我们需要动态的去建立该对象,因此我们需要在堆内存中创建对象和释放对象。在C语言中为我们提供了malloc()函数和free()函数来为我们提供在堆内存中分配变量的方式,但是在C++中引入了new和delete关键字来让我们动态的创建和释放变量。
new关键字是用来在堆内存中创建变量的,格式为:Type * ptr = new Type(常量/表达式); 其参数列表中的常量/表达式可以用来给变量初始化,也可以省略不写。其返回结果为该类型的指针。如果内存分配失败则返回空指针。
delete关键字是用来释放用new关键字创建的内存,格式为delete ptr(释放数组必须需要加中括号,delete [] ptr)。
new关键字在分配内存的时候,会根据其创建的参数调用相应的类的构造函数。delete关键字会在释放内存之前,会首先调用类的析构函数释放对象中定义的内存。
malloc和free关键字不会去调用类的构造函数和析构函数。
static关键字用来声明类中的成员为静态属性。当用static关键字修饰成员后,该类所创建的对象共享static成员。无论创建了多少个对象,该成员只有一份实例。静态成员是与类相关的,是类的一种行为,而不是与该类的对象相关。
静态成员是类所有的对象的共享成员,而不是某个对象的成员,它在对象中不占用存储空间,这个成员属于整个类,而不属于具体的一个对象,所以静态成员变量无法在类的内部进行初始化,必须在类的外部进行初始化。比如定义一个学生类,那么学生对象总数可以声明为static,在构造方法中,对该变量进行加1,从而统计学生对象的数量。
静态成员函数用static关键字定义,在静态成员函数中可以访问静态成员变量和静态成员函数,但不允许访问普通的成员变量和成员函数,因为普通的成员属于对象而不属于类。层次不一样。但是在普通成员中可以访问静态成员。
当静态成员函数在类中定义,但是在类的外面实现的时候,不需要再加static关键字。
静态成员函数没有this指针。
# include
using namespace std;
class MyStudent
{
private:
static int count;/* 学生对象总数 */
char name[64];
int age;
public:
static int n;
public:
MyStudent(char * name,int age)
{
strcpy(this->name, name);
this->age = age;
MyStudent::count++;/* 学生数量加1 */
}
void getCount()/* 普通成员函数访问静态成员变量 */
{
cout << "学生总数:" << MyStudent::count << endl;
}
};
/* 静态成员变量初始化 */
int MyStudent::count = 0;
int MyStudent::n = 10;
int main()
{
/* 测试静态成员变量 */
MyStudent student1("王刚",22);
student1.getCount();
/* 对象和类方式访问静态成员变量 */
student1.n = 100;
MyStudent::n = 200;
}
当我们定义类的时候,使用private关键字修饰成员变量(成员函数),这样做到了访问控制。有些时候,我们需要让一些函数来访问对象的私有成员(属性或方法),C++为我们提供了友元函数这个概念,所谓的友元函数就是指这个函数是这个类的好朋友,允许让这个函数访问这个类创建的对象的私有属性和私有方法。友元函数用friend函数来声明,友元函数的声明必须在类的内部,友元函数的实现必须要在类的外部(如果友元函数的实现也在内部,那还要用友元函数干什么?),友元函数的声明位置与访问控制符无关。
# include
using namespace std;
/* 定义点类 */
class Point
{
private:
int x;
int y;
/* 友元函数的定义:求两点的距离 */
friend int distance(Point &p1, Point &p2);
public:
Point(int x, int y)
{
this->x = x;
this->y = y;
}
};
/* 友元函数的实现 */
int distance(Point &p1, Point &p2)
{
int dx = p1.x - p2.x;
int dy = p1.y - p2.y;
return sqrt(dx*dx + dy*dy);
}
int main()
{
Point p1(3, 4);
Point p2(0, 0);
int dis = distance(p1, p2);
cout << "点(3,4)到原点的距离为:" << dis << endl;
}
若B类是A类的友元类,则B类的所有成员函数都是A类的友元函数。类B可以访问类A的所有私有属性和方法。
友元类通常被设计为一种对数据操作或者类之间传递消息的辅助类。
# include
using namespace std;
/* 定义类A */
class A
{
private:
int x;
friend class B;/* 定义类B为类A的友元类 */
private:
void setX(int x)
{
this->x = x;
}
};
/* 定义类B */
class B
{
private:
A AObj;
public:
/* 类B的所有成员函数都是类A的友元函数,因此都可以访问类A的私有属性和方法 */
void operater(int tmp)
{
AObj.setX(tmp);
}
void display()
{
cout << "类A的私有属性x = " << AObj.x << endl;
}
};
int main()
{
B b;
b.operater(100);
b.display();
return 0;
}
1.内存模型分类
2.理解函数,代码和内存分配的问题
函数是由一条条的操作指令(代码)组合而成的,其指令可以是定义变量向内存申请空间,或者是操作指令,例如两数相加。
1.C++中类和对象
在C语言中,“数据”和“函数”是分开来声明的,也就是说,语言并没有支持“语言和函数”之间的关联性。但是在C++中却提供了“类”来将“数据”和“函数”绑定在了一起。C++中的类由成员变量,静态成员变量,构造函数,成员函数,静态函数和虚函数组成。下面我们来研究组成类的数据和函数是如何在内存中分配的。
2.C++中是如何管理类和对象的 我们首先来定义几个类,然后对下面的代码进行分析。
# include
using namespace std;
/* 定义第一个类:12个字节 */
class C1
{
private:
int a;// 4
int b;// 4
int c;// 4
};
/* 定义第二个类:12个字节 */
class C2
{
private:
int a;// 4
int b;// 4
int c;// 4
static int d;// 0
};
/* 定义第三个类:4个字节 */
class C3
{
private:
int a;// 4
static int b;// 0
public:
void setA(int a)// 0
{
this->a = a;
}
int getA()// 0
{
return this->a;
}
static void add()// 0
{
C3::b += 100;
}
};
/* 定义结构体:4个字节 */
struct S1
{
int a;
static int b;
};
int main()
{
cout << "C1类所占字节数:" << sizeof(C1) << endl;
cout << "C2类所占字节数:" << sizeof(C2) << endl;
cout << "C3类所占字节数:" << sizeof(C3) << endl;
cout << "S1结构体所占字节数:" << sizeof(S1) << endl;
return 0;
}
3.上述代码结果分析
4.C++成员变量存放在对象中,那么成员函数是如何管理的?
5.上述代码总结
C++的类是用结构体实现的,因此类中的成员变量和结构体里的变量内存布局方式一致。
C++类中的成员函数隐式包含一个指向当前对象的指针,哪个对象调用该函数,C++编译器就默认将该对象包含在函数中。
静态成员变量和静态成员函数属于类,因此静态成员函数不包含指向具体对象的指针。
6.this指针
通过上述示例,我们发现类中的普通成员函数包含一个this指针,该指针指向调用当前函数的对象,即谁调用我,我就指向谁。
this指针通常用于在成员函数或者构造函数的形参和成员变量一致的时候,用this指针来表示当前对象的成员变量,形参直接赋值给成员变量即可。
文章内容来源于:
https://www.cnblogs.com/metalsteel/p/6271239.html
https://www.cnblogs.com/metalsteel/p/6270356.html
https://www.cnblogs.com/metalsteel/p/6266505.html