类的引入:
类的简单定义
- C语言中,结构体只定义变量,在C++中,结构体不仅可以定义变量,也可以定义函数。
同时我们应该注意到:C++是兼容C的,所以在C++中,通常使用struct/class 表示类。
如下代码所示,主要举例的是C++兼容C的功能:
#include<iostream>
#include<map>
#include<assert.h>
#include<string>
using namespace std;
//在项目中为了防止命名污染,通常不展开std;
//通常使用例如:
//using std::cout; using std::endl;等
//struct /class在C++中表示类
struct Student
{
char name[20];
int age;
int id;
};
int main()
{
struct Student s1; //C中结构体的表示方法
Student s2; //C++中类的表示方法。
strcpy(s1.name, "张三");
s1.age = 18;
s1.id = 1;
strcpy(s2.name, "李四");
s2.age = 19;
s2.id = 2;
cout << s1.name << " " << s1.age << " " << s1.id << endl;
cout << s2.name << " " << s2.age << " " << s2.id << endl;
return 0;
}
运行结果如下:
类的定义:
通常情况下,我们利用下列结构来表示类。
class className
{
//成员函数和成员变量
};
其中class表示类的关键字,className表示类名,通常类中的成员变量和成员函数通常使用访问控制符来限制访问。
访问控制符包括:private、public、protected。
同时我们应该掌握面向对象的三大特征:封装、继承和多态。
而在通常情况下C++中的函数通常也和类封装在一起,如下代码所示:
class Student
{
private:
char _name[20];
int _age;
int _id;
public:
void Init(const char* name, int age, int id)
{
strcpy(_name, name);
_age = age;
_id = id;
}
void Print()
{
cout << _name << endl;
cout << _age << endl;
cout << _id << endl;
}
};
int main()
{
Student s1;
s1.Init("张三", 16, 1);
s1.Print();
cout << endl;
Student s2;
s2.Init("李四",17,2);
s2.Print();
return 0;
}
运行结果如下:
类的两种定义方式
可以在头文件中定义类,也可以直接在cpp文件中定义类:
两种定义方法如下:
- 在头文件中定义:
头文件:
#pragma once
#include<iostream>
#include<map>
#include<assert.h>
#include<string>
using namespace std;
class Person2
{
public:
void Init(const char* name, int age,int id);
void showInfo();
public:
char _name[20];
int _age;
int _id;
};
cpp文件中Person2成员函数的具体实现如下:不要忘记在CPP文件中引用头文件 #include"Person.h"
值得注意的是: 声明和定义全部放在类体中,成员函数如果在类中定义,编译器可能会将其当成内联函数处
理。
类的作用域: 类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
void Person2::showInfo()
{
cout << _name << "-" << _age << "-" << _id << endl;
}
void Person2::Init(const char* name,int age,int id)
{
strcpy(_name, name);
_id = id;
_age = age;
}
- 直接在cpp文件中定义类:
class Person1
{
public:
void showInfo()
{
cout << _name << "-" << _age << "-" << _id << endl;
}
void Init(const char* name, int age,int id)
{
strcpy(_name, name);
_id = id;
_age = age;
}
public:
char _name[20];
int _age;
int _id;
};
类中的访问限定符及封装
C++实现封装的方式:用类将对象的属性与方法结合到一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
访问限制符:
我们在上文就已经提到有三种访问限定符为:private、protected、public。
访问限定符的说明:
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限制符只在编译的时候有用,当数据映射到内存后,没有任何访问限制符的区别。
问题: C++中struct和class的区别是什么?
解答: C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是
private。
封装:
定义: 封装实质上是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互。
对象的实例化:
用类类型创建对象的的过程称为对象的实例化。
- 类只是一个模型,限定类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
- 一个类可以实例化多个对象,实例化出的对象占用实际的物理空间,存储类的成员变量。
例如以下代码: - 定义一个类:
class Person
{
public:
void showInfo()
{
cout << _name << "-" << _age << "-" << _id << endl;
}
/*
void Init(const char* name, int age,int id)
{
strcpy(_name, name);
_id = id;
_age = age;
}
*/
public:
char _name[20];
int _age;
int _id;
};
- 实例化类:
void Test()
{
Person man;
man._name="张三";
man._age=18;
man._id=1;
man.showInfo();
}
####类对象模型
类对象的存储方式
给出下面的代码,给出下面的代码如下:
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A aa;
A bb;
cout << &aa << endl;
cout << &bb << endl;
return 0;
}
运行如下:
再根据以下代码如图所示:
class A1
{
public:
void f(){}
};
class A2
{
};
class A3
{
public:
int _age;
char _sex;
int id;
};
int main()
{
A1 a1;
A2 a2;
A3 a3;
cout << sizeof(a1) << endl;
cout << sizeof(a2) << endl;
cout << sizeof(a3) << endl;
return 0;
}
运行代码如下:
可以得出如下结论: 一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比
较特殊,编译器给了空类一个字节来唯一标识这个
结构体对齐规则:
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
this指针
我们给出一个简单的日期类如下:
class Date
{
public:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.SetDate(2021, 1, 23);
d2.SetDate(2021, 1, 24);
d1.Display();
d2.Display();
return 0;
}
运行结果如下:
对于上述的类中,有SetData()和Display()两个函数,函数体中并没有关于不同对象的区分,当d1调用SetDate()函数的时候,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
答: C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成,该指针称为:this指针。
this指针的特性:
- this指针的类型: *const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户
传递
this指针对用户透明,所以我们正常的的函数为:
void Display()
{
cout<<_year<<endl;
}
而实质上为:(切记在敲代码的时候不能按下面的代码写)
void Display(Date* this)
{
cout<<this->_year<<endl;
}
同时在main函数中:d1.Display(&d1); d2.Display(&d2);
关于this指针的题源:
eg:
class A
{
public:
void show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* pa=nullptr;
pa->show();
return 0;
}
运行结果如下:
代码如下:
class B
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
B* pb = nullptr;
pb->PrintA();
return 0;
}
运行结果:
分析结果:
- 对于A中的show函数中:cout<<“Show()”<<endl;没有对this指针解引用, 所以程序能够正常运行。
- 对于B中Print函数中:pb虽然是空指针,但是调用pb的成员函数不会编译出错(语法出错的时候才会报编译错误),因为空指针不是语法出错,编译器检查不出来。
- pb虽然是空指针,但是pb调用成员函数也不会出现空指针访问,因为成员函数没有存在对象里面
- 这里的this->_a有解引用,所以会发生运行崩溃。
this变量一般情况下在栈(形参)中,有些编译器也会放在寄存器中。