江苏环泰建设有限公司网站,软件外包价格,wordpress后台打开很慢,福建省建建设行业信用评分网站提示#xff1a;本文主要介绍C中类相关知识及基础概念总结 渺渺何所似#xff0c;天地一沙鸥 文章目录 一、面向对象与面向过程二、类的框架知识2.1 类的定义2.2 类的封装性2.2.1 访问限定符2.2.2 封装的概念以及实现 2.3 类的作用域及实例化2.4 类中this指针 三、六大默认成… 提示本文主要介绍C中类相关知识及基础概念总结 渺渺何所似天地一沙鸥 文章目录 一、面向对象与面向过程二、类的框架知识2.1 类的定义2.2 类的封装性2.2.1 访问限定符2.2.2 封装的概念以及实现 2.3 类的作用域及实例化2.4 类中this指针 三、六大默认成员函数3.1 构造函数3.2 拷贝构造函数3.3 析构函数3.4 赋值运算符重载 四、类中const成员与static成员五、破坏封装性的友元函数 一、面向对象与面向过程
类是C一切功能实现的载体繁华的功能实现都依托于类这颗苍天大树之上。 而C于C语言最根本的不同就在于C是面向对象的C将一件繁杂的业务拆分成不同的对象靠一个个精密的对象之间交互完成这个业务而C语言确实关于问题的过程分析出求解问题的步骤通过一个个函数逐步的刨析调用最终得以解决问题。
二、类的框架知识
2.1 类的定义
在C中使用关键字class来定义类
class kind
{
//共有成员
public:
//保护成员
protected:
//私有成员
private:
}不同于C语言中的struct 在类中可以通过访问限定符的修饰来定义不同权限的函数这也是C中一个很大的改变那什么才是访问限定符呢它又有什么样的作用
2.2 类的封装性
2.2.1 访问限定符
C是一个面向对象的语言那么他就存在权限的说法不然人人都可以访问那和C语言又有什么区别呢 所以C的类中引入了三种访问限定符 public 成员可以在类的外部通过对象访问。 protected 成员可以在类的派生类中访问这个主要在继承体系中使用因为被它所修饰的成员不能再类外访问但是可以在类的派生类中通过成员函数来访问基类的保护类成员。 private 成员只能在类内访问。 而当我们在创建类时不进行访问限定符的修饰时默认的访问限定符为private类型在C中struct也可以当作class来使用且struct的默认访问限定符为public类型这个主要是为了兼容C语言。 所以在C中我们可以理解为class与struct在默认访问限定符有区别外其他方面没有任何区别
2.2.2 封装的概念以及实现
封装就是将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节进对外公开接口来和对象进行交互。 当然封装一方面是为了安全性另一方面也是为了简化我们操作的难度降低我们看到的代码的冗余性不然若是暴漏所有的实现细节那么在文档的查询阅读等方面可能要花费更多的时间与精力这几乎是不可完成的。 C中如何实现封装呢 先通过类将对象的属性和方法包装成一个整体表明包装类实现的是什么功能然后在内部通过访问限定符选择性的将接口暴漏给使用者然后完成交互比如常见的视频平台会员机制 普通用户与VIP的界面都是不同的
#include iostream
#include stringclass Car
{
private:std::string brand;std::string model;
public:Car(const std::string carBrand, const std::string carmodel) :brand(carBrand), model(carmodel) {}void getbrand(){std::cout 车型 brand std::endl;std::cout 车类 model std::endl;}
};int main()
{Car mycar(benchi, 365);mycar.getbrand();return 0;
}Car内部的两个私有接口不可以直接访问到然后通过类内共有接口访问输出汽车信息。较为简单主要为了体现出类的封装性类内调用。
2.3 类的作用域及实例化
类有自己独立作用域类的所有成员都在类的作用域中通俗的讲类的作用域指定类定义在哪个范围内是可见的一般可以是全局范围、命名空间、函数内部、或者其他类成员函数内部。 全局作用域及其实例化
// muclass.h内
class MyClass
{//类功能
}
#include ”MyClass.h“
int main()
{MyClass myobject; // 在全局作用域内都可以使用// ... return 0;
}这种就是不加以任何的修饰以.h头文件的方式定义然后直接在主函数中定义展开使用是全局的。 命名空间作用域及其实例化
// test.h文件
namespace Myname
{class MyClass{// 具体的类定义}
}
// test.cc 文件
#include test.h
int main()
{// 使用命名空间限定符实例化对象Myname::MyClass class; // 必须展开.h文件// ... 具体的操作
}在命名空间内定义的类可以在该命名空间及其子命名空间中访问。 函数作用域内及其实例化
void Myfunc()
{class MyClass{// 具体的类定义 ...}MyClass myobject; // 仅可以在函数内实例化对象// ... 具体的操作
}在函数内定义的局部类只能在该函数内部定义 类的成员函数作用域及其实例化
// 通俗易懂 类的成员函数作用域肯定是被类中访问限定符所修饰的
class MyClass
{
public:void mytest(){// 类内调用类的其他成员变量以及函数int x mytest;Myfunc();}
private:int mytest;void Myfunc();// ...
}上边分别展示了四种作用域以及作用域中实例化的方式。 类在我看来就是一类具有公共属性的集合体。是一种集合的对象使用多种单个对象结合起来而我们在对类进行具体的实例化时可以看作同时实例化了一批我们所需要用到的对象同样会占用空间同样要受到访问权限的约束类就像一个巨大的模板而对象便是组成这个模板的个体最终变成了一个巨大的个体 类就是对数据和行为的封装和抽象 类的实例化中我们要了解到的一些小知识点 1.一个类的大小如何计算 2.空类的大小是多少 3.什么是内存对齐结构体是如何进行内存对齐的 4.如何让结构体按照指定的默认对齐数进行对齐 5.如何知道结构体中某个成员相对于结构体起始位置的偏移量 2.4 类中this指针
在函数体中所有的成员变量操作都是通过this指针去访问的。 this指针的特性 1.this指针不可改变所以在类类型为 *const 。 2.只能在 “成员函数” 的内部使用。 3.this指针本质上是一个成员函数的形参在对象调用成员函数时第一个就是隐藏的this指针参数将对象的地址作为实参传递给this形参所以在对象内存中 并不存储 this指针。 4.this指针是 成员函数 第一个隐含的指针形参一般情况由编译器通过ecx寄存器自动传递不需要用户传递。 #include iostream
#include stringclass Myclass
{
private:int _x;int y;int z;
public:Myclass(int x, int y,int z){_x x; // 当命名不冲突时可以直接使用命名赋值this-y y; // 当命名冲突时可以使用this指针显示的调用赋值z z; // 错误的方式不会成功}void print(){std::cout _x: _x \ny: this-y z std::endl;}
};int main()
{Myclass c(1, 2,3);c.print();return 0;
}可以看到_x,y都成功赋值但是z却没有他们都有各自的地址空间但就是z没有初始化成功。所以当在成员函数中对成员变量进行操作时可以显示的调用this指针来进行操作。 切记成员函数调用的时候第一个传递的参数是隐藏的this指针
三、六大默认成员函数
3.1 构造函数
构造函数是特殊的成员函数是类中独有的且构造函数名与类名相同创建类对象时由编译器自动调用在对象的生命周期内仅调用一次。 在编写类时不显式定义构造函数编译器会提供默认的构造函数但默认构造函数不接受任何参数他只是执行一些默认初始化操作。 在函数创建时构造函数会为成员变量分配内存设置默认值或者执行一些其他的初始化逻辑。 一个类可以显示的创建多种类的构造函数他们具有不同的参数列表称为构造函数的重载就可以在构造对象时根据不同的参数提供不同的初始化方式。
#include iostream
#include stringclass Person
{
private:std::string _name;int _age;
public:Person() // 无参构造{_name 张三;_age 18;std::cout Person()\n;}Person(const std::string name,int age):_name(name),_age(age) // 具体参数且使用初始化列表构造{std::cout Person(const std::string name,int age) std::endl;}void print(){std::cout name: _name Age: _age std::endl;}
};int main()
{Person p1;p1.print();Person p2(李四, 19);p2.print();return 0;
}上边就是显示多构造函数然后构造函数重载两种方法都可以成功创建对象切初始化成功。 在构造函数中还有个独有的模块 初始化列表 可以对类的成员变量进行初始化有基类特定成员必须在初始化列表中初始化 1.常量成员变量const因为常量成员变量的值无法在构造函数体内修改 2.引用成员变量引用成员变量必须在创建对象时引用有效的对象 3.类类型对象该类没有默认构造函数 #include iostream
#include stringclass Myclass
{
private:const int _num;int _val;
public:Myclass(int num, int val) :_num(num), _val(val) {// ---}
};
int main()
{int val 5;// _num只需要传递同类型的常量过去就好而_val必须传递一个具有实际地址空间的同类型量因为它实例化的是一个引用而引用的定义便需要一块实际存在的空间Myclass s(6, val); return 0;
}而其他的非常量非引用的成员变量看个人习惯若是直接传入的可以直接在初始化列表进行初始化,且建议在初始化列表初始化成员变量在类中声明的次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关。 注意 1.构造函数不能用const修饰 2.构造函数不能是虚函数 3.无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能由一个。 3.2 拷贝构造函数
什么是拷贝构造函数 拷贝构造函数的概念是只有单个形参且该形参是对本类类型对象的引用一般常用const修饰在用已存在的类类型对象创建新对象时由编译器自动调用。 他就好像构造函数的一种重载形式所以构造函数的特性拷贝构造函数都满足拷贝构造函数的参数只有一个且必须使用引用传参使用传值的方式就会引发无穷递归调用。 他和构造函数一样也是类的默认函数之一若未显示的定义系统会自动生成默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝一般称为浅拷贝或者值拷贝只拷贝值过去。 因为是默认是浅拷贝那当我们的类中存在指针变量时两个原本的对象内容都会收到修改此时就需要我们手动进行深拷贝了。
class Myclass
{
private:int* _data;
public:Myclass(int data){_data new int;*_data data;}/*Myclass(const Myclass data){_data new int;*_data *(data._data);}*/void print(){*_data 2;std::cout _data: *_data std::endl;}
};int main()
{Myclass s(1);Myclass ss(s);s.print();return 0;
}当我们运行 void print(); 函数时仅仅改变s内的datass中的也被改变这就是因为他们中私有成员_data指向的是头一块地址。而若是我们实例化拷贝构造函数就会出现如下图 当改变s中值时对ss中值不影响。
// 手动进行类的析构函数
~Myclass(){delete _data;}同样一段代码若显示的调用虚构函数对s的内容进行delete此时ss中信息也就被释放这样会造成内存卸扣已经一些未知的安全问题。 总结 编译器生成的拷贝构造函数是浅拷贝将对象中内容原封不动的拷贝到新对象中若是原对象中涉及到了资源管理那么新对象和原对象共用的就是同一份资源在进行赋值或者析构时会造成内存泄露问题或者是程序崩溃。
3.3 析构函数
析构函数与构造函数的功能相反析构函数不是完成对象的销毁局部对象销毁工作是由编译器完成的而对象在销毁时会自动调用析构函数完成类的一些资源清理工作。 析构函数的名称与类名相同前面加上一个波浪号~作为前缀并且析构函数不接受任何参数也无返回值析构函数在以下情况下会被调用 1.当对象的生命周期结束时例如对象超出作用域或被显式的删除。 2.当对象作为另一个对象的成员被销毁时。 3.当对象在类上通过new运算符分配时然后通过delete运算符删除时。 4.若是类中显示的使用new进行资源申请那么若是不在析构函数中定义释放就会造成资源泄露。 若是显示的实例了析构函数那么就应该在对象销毁之前释放所分配的动态内存、关闭打开的文件、释放占用的其他资源等。 同时建议基类的析构函数最好设置成虚函数。 class Base
{
public:Base(){std::cout Base constructor std::endl;}virtual ~Base(){std::cout Base destructor std::endl;}
};
class Derived : public Base
{
public:Derived(){std::cout Derived constructor std::endl;}~Derived(){std::cout Derived destructor std::endl;}
};int main()
{Base* s new Derived;delete s;return 0;
}若是不将基类设置为虚函数那么就会出现以下情况 在delete基类指针的派生类对象时并不会堆派生类进行释放导致内存泄漏。 在手动释放资源时可以看出基类与派生类的堆资源都被释放。 之前在多态中我们虚函数的定义是函数名一定要相同才有可能构成虚函数而析构函数是一种例外虚函数是一种动态多态是在运行时实现了而析构函数的虚函数就是其中的一种例外所以最好将基类的析构函数声明成虚函数。
3.4 赋值运算符重载
在C中为了代码的可能性引入了通过operator关键字来支持运算符重载运算符重载是针对自定义类型的。 通常的写法为
operator重载运算符参数列表
{然后进行处理
}赋值运算符也可以当作一种特殊的函数用于将一个对象的值赋给另一个对象。 注意事项 1.不能通过连接其他符号来创建系的操作符例如operator 2.重载操作符必须有一个类类型或者枚举类型的操作数 3.用于内置类型的操作符的含义不能改变例如内置的整形- 4.作为类成员的重载函数时其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this限定为第一个形参。 常见运算符重载 1.赋值运算符重载 2.、–重载 3.输出和输出运算符 重载 4.[ ]运算符重载 5.*和-重载 6.()重载 下面是这些运算符重载实例
#include iostream
#include stringclass student
{
public:int _chinese;int _math;int _english;int* _data;std::string _name;
public:student(int data,int chinese,int math,int english,std::string name):_chinese(chinese),_math(math),_english(english),_name(name){_data new int;*_data data;}void operator(const int tmp){if (this-_chinese ! tmp){_chinese tmp;}}void operator(){this-_english;}void operator--(){this-_math--;}int* operator*(){return this-_data;} void print(){std::cout *_data_chinese _math _english _name std::endl;}
};int main()
{student stu(66,10, 15, 5, 张三);stu.print();student stu1(66,11, 16, 5, 小徐);stu1.print();stu1 stu._chinese;stu1._math--;stu1._english;int* mstu1.operator*();std::cout *m std::endl;return 0;
}一部分赋值运算符重载实现案例。
默认赋值运算符重载 1.如果一个类没有显示定义赋值运算符重载编译器会生成一个默认的赋值运算符重载。 2.编译器生成的默认赋值运算符重载是按照浅拷贝方式生成的。 3.如果类中涉及到资源管理时用户必须显示提供赋值运算符重载否则可能会造成内存泄露或运行时崩溃用户按照深拷贝方式提供。 四、类中const成员与static成员
在类中成员一般大的可以分为三类 普通成员变量 这一般表示该变量是一个常量并且在编译阶段会进行参数类型检测以及替换比宏常量更安全因此可以用其取代宏常量。 const修饰类成员、函数 首先const变量const指的是此变量是只读的不应该被改变。 如果在程序中修改const变量的值在编译时编译器将给出错误提示。 而他的不可修改行就注定了const类变量必须被初始化。 const int val 10 const in val// 编译器报错未初始化const变量 val5 // 编译器报错给只读变量赋值 修饰成员函数时
// const修饰成员函数的声明
class Myclass
{
public:// 在类中用const修饰成员函数时是在函数的后面加上constvoid func() const{// 具体的实现}
}const成员函数的特性 1.const成员函数可以被const对象调用但不能被非const对象调用 2.const成员函数可以访问类的成员变量但不能修改他们 3.const成员函数可以调用其他const成员函数但不能调用非const成员函数除非用const_cast进行转换。 include iostream
#include stringclass Myclass
{
private:int _val;
public:Myclass(int val):_val(val){}// const类型成员函数int getval() const {return _val;}// 非const类型成员函数int setval(int val){_val val;}
};int main()
{const Myclass m(10);std::cout m.getval() std::endl;m.setval(20); // 编译失败因为m是const对象不能调用非const成员函数return 0;
}当const修饰成员函数的返回值时
#include iostream
#include stringclass Myclass
{
private:int _val;
public:Myclass(int val):_val(val){}
public:// 在类中const int 和 int const 成员函数返回的是一个常量整数值 // const int* 和 int const* 返回的是一个常量整数的指针// const int 和 int const 返回的是一个只读的整数引用// 这三组的意义都是相同的const int getval(){return _val;}const int getval1(){return _val;}
};int main()
{Myclass s(10);std::cout _val: s.getval() std::endl;std::cout _val: s.getval1() std::endl;return 0;
}对于常量值和常量对象的声明使用const关键字的位置可以放在类型之前或之后效果是一样的。这是因为const关键字修饰的是类型本身而不是修饰具体的标识符。 static修饰类成员变量、函数 在类中被static修饰的成员称为静态成员。 被static修饰的成员变量 static成员变量知识 1.静态成员变量不能再初始化列表位置初始化必须在类外进行初始化在类外初始化时必须要加类名::类中知识声明。 2.静态成员变量是类的属性不属于某个具体的对象是类所有对象共享的。 3.不存在在具体的对象中因此不会影响sizeof的大小 4.可以通过对象.静态成员名也可以通过类名::静态成员变量名方式访问。 5.在程序启动时就完成了对静态成员变量的初始化工作。 #include iostream
#include stringclass Myclass
{
public:static int _level;Myclass(int val):_val(val){}~Myclass(){}void print(){std::cout _level: _level _level1: _level1 std::endl;}
private:int _val;static int _level1;
};
// 类中共有static变量初始化
int Myclass::_level 5;
// 类中私有static变量初始化
int Myclass::_level1 6;int main()
{Myclass s(5);s.print();// 大小计算无影响std::cout sizeof(s) std::endl;// 类名::访问std::cout Myclass::_level: Myclass::_level std::endl;int m s._level;// 对象.访问std::cout s._level: m std::endl;return 0;
}从输出可以看出对类的大小static变量不会占用空间因为无论是类内还是内外静态变量都是存储在静态数据区的而普通的成员变量是存储在各个对象的内存中若是通过new开辟的成员变量那就再堆空间上其他在栈空间上。
被static修饰的成员函数 static修饰成员函数知识 1.静态成员函数没有this指针 2.静态成员函数中不能直接访问非静态成员变量因为所有非静态成员变量都是通过this指针访问的。 3.静态成员函数不能调用普通成员函数。 4.静态成员函数不能被this修饰。 5.静态成员函数不能是虚函数。 6.既可以通过对象也可以通过类名::方式访问。 #include iostream
#include stringclass Myclass
{
public:Myclass(int val):_val(val){}~Myclass(){}void print(){std::cout _val std::endl;}static int add(int a, int b){return a b _val1;// 静态成员函数不能直接访问非静态成员变量。非静态成员变量都通过this指针调用// return a b _val; // print(); // 静态成员函数不能调用普通成员函数}// virtual static int add1(int a, int b)//{// // 静态成员函数不可以是虚拟的//}
private:int _val;static int _val1;
};int Myclass::_val1 5;
int main()
{Myclass s(3);s.print();// 通过对象调用静态成员函数std::cout s.add(1,2): s.add(1, 2) std::endl;// 通过类名调用静态成员函数std::cout Myclass::add(1,2): Myclass::add(1, 2) std::endl;return 0;
}五、破坏封装性的友元函数
概念友元提供了一种突破封装的方式有时提供遍历但是友元破坏了三大特性中的封装 友元函数友元函数可以直接访问类的私有成员它是定义在类外部的普通函数不属于任何类单需要在类内部声明。 特性 1.友元函数可访问类的私有和保护成员但不是类的成员函数。 2.友元函数不能用const修饰 3.友元函数可以在类定义的任何地方声明不受类访问限定符限制 4.一个函数可以是多个类的友元函数。 5.友元函数的调用与普通函数的调用和原理相同 // 定义
#include iostream
class Myclass
{
private:int _val;
public:Myclass(int val):_val(val){}friend void showval(const Myclass tmp);
}
void showval(const Myclass tmp)
{
//具体实现
}注意 1.友元关系是单向的不具交换性。 2.友元关系不能传递。 3.友元关系不能继承。