C++与C不同,C是面向过程编程,在进行编程时,首先需要考虑构建一个过程,对输入进行处理得到输出。C++是面向对象编程,在进行编程时,首先需要考虑构建一个对象,让这个对象模型配合相对应的问题,通过获取对象的信息得到输出。
对象:将数据和对数据的操作放在一块,将其作为一个相互依靠的整体,称之为对象。
对同类对象的特性抽象出来,类中的绝大多数数据只能通过类的方法进行操作。
C++面向对象的三大特性:封装、继承、多态。
封装的意义:
将一事物的属性和行为抽象出来作为一个对象,将其属性私有化,行为公开化,提高了数据的隐蔽性,隐蔽了类的实现细节和成员数据,实现了代码模块化,提高了复用性。
Class A{
//属性
//行为
};
类中的属性和行为统称为成员。属性:成员属性、成员变量。行为:成员函数、成员方法。
类在进行设计时,可以把属性和行为放在不同的权限下,加以控制。一共有三个访问权限:
1.public公共权限,类内可以访问,类外也可以访问。
2.protected保护权限,类内可以访问,类外不可以访问。
3.private私有权限,类内可以访问,类外不可以访问。
对象的初始化和清理:
对象的初始化和清理是两个非常重要的安全问题。一个对象或者变量没有初始化,对其使用的后果是未知的。同样的使用完一个对象或者变量,没有进行及时清理,也会造成一定的安全问题。
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对对象的初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事,因此如果我们没有提供构造函数和析构函数,编译器会提供构造函数和析构函数(构造函数和析构函数都是空实现)。
构造函数和析构函数:
构造函数:主要作用是在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用是对象在销毁前系统自动调用,执行一些清理工作。
构造函数的语法:
1.构造函数,没有返回值,不写void。
2.函数名称与类名相同。
3.构造函数可以有参数,因此可以发生重载。
4.程序在调用对象时候会自动调用程序,无需手动调用,而且只会调用一次。
Class A{
A(){
}
};
析构函数的语法:
1.析构函数,没有返回值,不写void。
2.函数名称与类名相同,在名称前加上符号~。
3.析构函数不可以有参数,因此不可以发生重载。
4.程序在对象销毁前自动调用析构,无需手动调用而且只会调用一次。
Class A{
~A(){
}
};
注意:构造函数和析构函数都是必须有实现的,如果不提供,编译器会提供一个空实现的构造和析构函数。
构造函数的分类以及调用:
两种分类方式:
按参数方式:有参构造和无参构造(默认构造函数)。
按类型分类:普通构造和拷贝构造。
三种调用方式:
1.括号法。
2.显示法。
3.隐式转换法。
class person{
public:
int val;
public:
person(){
cout<<"无参构造" <<endl;
}
person(int a){
cout<<"有参构造" <<endl;
}
person(const person &p){
cout<<"拷贝构造" <<endl;
}
~person(){
cout<<"析构函数" <<endl;
}
}
void test01(){
括号法:
person p1;//调用默认构造函数(无参构造)
person p2(10);//调用有参构造函数
person p3(p2);//调用拷贝构造函数
注意:调用默认构造函数时不要加(),如:person p1();
因为编译器会认为person p1();是一个函数声明,不会认为在创建对象
显示法:
person p1;//无参构造
person p2=person(10);//有参构造
person p3=person(p2);//拷贝构造
person(10):匿名对象
匿名对象特点:当前行执行结束,系统会立即回收掉匿名对象
注意:不要利用拷贝函数初始化匿名对象
person(p3);//编译器会认为person (p3)=person p3;认为是声明
隐式转换法:
person p4=10;//相当于写的:person p4=person(10):有参构造
person p5=p4;//拷贝构造
}
拷贝构造函数调用的时机:
C++中拷贝函数调用时机通常有三种情况:
1.使用一个已经创建完毕的对象来初始化一个新的对象。
#include<iostream>
using namespace std;
class Person {
public:
int val;
public:
Person() {
cout << "无参构造" << endl;
}
Person(int a) {
val = a;
cout << "有参构造" << endl;
}
Person(const Person& p) {
val = p.val;
cout << "拷贝函数" << endl;
}
~Person(){
cout << "析构函数" << endl;
}
};
void test01() {
Person p1(10);
Person p2(p1);
cout << "p2的val=" << p2.val << endl;
}
int main() {
test01();
return 0;
}
2.值传递的方式给函数参数传值。
#include<iostream>
using namespace std;
class Person {
public:
int val;
public:
Person() {
cout << "无参构造" << endl;
}
Person(int a) {
val = a;
cout << "有参构造" << endl;
}
Person(const Person& p) {
val = p.val;
cout << "拷贝函数" << endl;
}
~Person(){
cout << "析构函数" << endl;
}
};
void doWork(Person p) {
}
void test02() {
Person p1;
doWork(p1);
}
int main() {
test02();
return 0;
}
3.以值方式返回局部对象。
#include<iostream>
using namespace std;
class Person {
public:
int val;
public:
Person() {
cout << "无参构造" << endl;
}
Person(int a) {
val = a;
cout << "有参构造" << endl;
}
Person(const Person& p) {
val = p.val;
cout << "拷贝函数" << endl;
}
~Person(){
cout << "析构函数" << endl;
}
};
Person doWork2() {
Person p2;
return p2;
}
void test03() {
Person p1 = doWork2();
}
int main() {
test03();
return 0;
}
构造函数的调用规则:
默认情况下,C++编译器至少给一个类添加3个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,堆属性进行值拷贝
如果用户定义有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝函数。如果用户定义拷贝构造函数,C++不会再提供其他构造函数。
深拷贝和浅拷贝:
浅拷贝:
简单的复制拷贝操作。仅复制对象的成员值(包括指针的地址),不复制指针指向的实际数据。多个对象的指针成员共享同一块内存区域,修改任意一个对象的数据会影响其他对象。
行为:
1.默认的拷贝构造函数和赋值运算符执行浅拷贝。
2.若对象包含指针成员,对个对象可能指向同一块内存。
风险:
1.悬垂指针:一个对象析构后释放内存,其他对象指针失效。
2.双重释放:多个对象析构时重复释放同一块内存,导致程序崩溃。
使用场景:
1.不包含指针成员时,可直接赋值操作。
2.临时共享数据且无需修改原数据时。
Class A{
int* data;
A(int value){
data=new int(value);
}
~A(){
if(data!=NULL){
delete data;
data=NULL;
}
}
};
深拷贝:
在堆区重新申请空间,进行拷贝操作。复制指针成员指向的实际数据,而非仅复制地址。为指针成员分配独立内存,并完整赋值原指针指向的数据,确保对象间数据完全隔离。每个对象拥有独立的内存空间,修改操作互不影响。
行为:
1.需要手动实现拷贝构造函数和赋值运算符。
拷贝构造函数必须为指针成员分配内存,并复制数据。
赋值运算符需要先释放原内存再分配新内存,避免内存泄漏。
2.每个对象拥有独立的资源副本,避免共享内存。
使用场景:
1.类包含指针成员其指向动态分配的资源。
2.需要独立修改数据且避免内存冲突时。
Class A{
int * data;
A(const A& other){
data=new int(*other.data);
}
A& operator=(const A& other){
if(this!=&other){
delete data;
data=new int(*other.data);
}
return *this;
}
};
浅拷贝和深拷贝的核心区别就是内存管理:
| 特征 | 浅拷贝 | 深拷贝 |
| 内存共享 | 多个指针指向同一块内存 | 每个指针都拥有独立的内存空间 |
| 数据独立性 | 修改一个对象的数据会影响其他对象 | 对象间数据完全独立,互不影响 |
| 内存风险 | 容易导致双重释放或者悬垂指针 | 内存管理更安全,但是需要手动分配与释放 |
| 实现复杂度 | 编译器默认行为,无需手动实现 | 需自定义拷贝构造函数和赋值运算符 |
初始化列表:
Class A{
int number;
int * data;
A(int a,int b):number(a),data(&b){}
};
上述例子中,number(a)的值传递给构造函数中的第一个参数,data(&b)中的值,传递给构造函数中的第二个参数。
类可以作为其他类的成员:
C++类中成员是另一个类对象,称该成员是对象成员。
Class A{
public:
A(){}
A(string Name){
this->Name=Name;
}
~A(){}
string Name;
};
Class B{
public:
B(){}
B(string name,string Name):name(name),Name(Name){}
~B(){}
string name;
A Name;
}
void func(){
B b("A类","B类");
cout<<b.Name.Name<<b.name;
}
输出的结果是:B类A类
B类中的初始化列表中,Name传递给Name时,相当于A.Name=Name,所以第二个值"B类"就传递给了B类的成员变量A类Name。
总结:
当类作为其他类的成员变量时候,构造和析构应该注意先后顺序。
1.构造顺序:当其他类的对象作为本类的成员,先构造其他类的对象,再构造本身。
2.析构顺序:析构顺序与构造函顺序相反。
静态成员:
静态成员就是在成员变量之前加上关键字static,成为静态成员变量。静态成员只有一份存在于公共内存中。
Class A{
int a;
static int b;
};
普通成员变量:每一个类对象中都有一份属于自己的成员变量,相互独立,没有关联。普通成员与对象绑定,随对象的建立而建立,随对象的消亡而消亡,类似于局部变量。
静态成员特点:
1.一个类中的所有静态成员变量只有唯一一份,被所有类共享。
2.成员变量被声明为静态成员变量,就不统计该成员的字节数,静态成员变量在对象中不占用存储空间,只是一个声明,内存与全局变量在一个区域。
3.静态成员变量必须在类内加static声明,在类外定义初始化,定义时不需要加static,不能再构造函数的初始化列表中创建,也不能用初始化列表初始化,如果未初始化,默认为0。
4.静态成员变量的生命周期不依赖于某个类对象,伴随着整个程序的生命周期。
5.静态成员变量不依赖任何类对象(属于类但不属于对象),所以可以不实例化类对象就可以直接访问。
静态成员函数:
1.没有隐藏的this指针,在静态成员函数中,不能直接访问普通成员变量和普通成员函数,可以间接访问,通过手动传递对象的地址。
2.可以直接访问静态成员变量和静态成员函数。
3.修饰静态成员函数,只能在类中,不能再类外,因为外部实体函数前添加static与文件内的作用域功能static冲突。
4.调用方式:
通过对象进行访问或者通过类名进行访问。
class A {
public:
//静态成员变量也有访问权限
static int val;
};
int A::val = 100;
void test() {
A a;
A b;
//有两种方式进行访问:
//1.通过对象进行访问:
cout << a.val << endl;
b.val = 200;
cout << b.val << endl;
//2.通过类名进行访问:
cout << A::val << endl;
//所有对象共享同一份数据,只要其中一个对其值进行改动,所有对象进行访问的值都会改变
}
静态成员的作用:
1.静态成员相当于多了一层类作用域的全局变量、全局函数。
2.静态成员变量适合存储所有类对象的公共属性。
3.静态成员函数可以当作访问私有的静态成员变量的公开接口,一方面不破坏类的封装性,另一方面可以不需要实例化对象也能调用类的静态成员,让类本身具有管理自己的能力。
类的占用内存空间大小:
C++中,类本身是一个类型定义,不直接占用内存,只有当实例化时,实例化对象才会真正占用内存。所以当调用sizeof(类名),实际上是在询问一个实例化对象的内存大小,而不是类本身的内存大小。
成员变量和成员函数时分开存储的。
非静态成员变量属于类的对象上;静态成员变量不属于类的对象上;静态成员函数不属于类的对象上;非静态成员函数不属于类的对象上。
空类的占用内存空间大小:
//成员变量和成员函数时分开存储的
class A {
public:
};//空类
void test() {
A a;
cout << "类A的存储空间大小=" << sizeof(a) << endl;
}
因为C++编译器会给每一个空对象分配一个字节的空间,是为了区分空对象占用内存的位置。每个空对象也有一个独一无二的内存地址。
非空类占用内存空间大小:
class A {
public:
int val;//非静态成员变量,属于类的对象上
static int num;//静态成员变量,不属于类的对象上
static void func(){}//静态成员函数,不属于类的对象上
void func1(){}//非静态成员函数,不属于类的对象上
};
void test() {
A a;
cout << "类A的存储空间大小=" << sizeof(a) << endl;
}
类内部的成员变量:
普通的变量:要占用内存,但是要注意对其原则。
static修饰的静态变量:不占用内存,因为编译器将其放在了全局变量区域。
类内部的成员函数:
普通函数:不占用内存。
虚函数:有一个指向虚函数的指针,要占用4个或者8个字节(根据系统位数来决定),用来指定虚函数的虚拟函数表的入口地址,所以一个类的虚函数所占用的内存地址是不变的,和虚函数的个数无关。
&spm=1001.2101.3001.5002&articleId=147503892&d=1&t=3&u=33fa15ff910f4af1975adfcbaba03e18)
1018

被折叠的 条评论
为什么被折叠?



