成都做seo网站公司,关键词seo如何优化,wordpress会员付费,临沂做wish网站1.面向过程和面向对象初步认识
我们知道#xff0c;C语言是面向过程的#xff0c;关注的就是问题解决的过程#xff1b;
C是面向过程和面向对象混编#xff0c;因为C兼容了C语言#xff0c;而面向对象关注的不再是问题解决的过程#xff1b;
而是一件事情所关联的不同…1.面向过程和面向对象初步认识
我们知道C语言是面向过程的关注的就是问题解决的过程
C是面向过程和面向对象混编因为C兼容了C语言而面向对象关注的不再是问题解决的过程
而是一件事情所关联的不同对象。
在点外卖时我们会经常使用饿了么/美团而它们对应的就是一个点餐系统面向过程和面向对象在这个系统中关注的对象是不同的。
面向过程的语言会关注点餐、接单、以及骑手接单等功能函数的实现而面向对象关注的是商家、用户、骑手之间的关系用户点餐商家出餐然后交由骑手配送。
2.类的引入
提到类很多学过C语言的朋友就会联想到结构体C兼容C语言——C语言中的struct在C中同样适用。
struct student
{char* name;int age;
};C在兼容struct的同时将struct升级成了类。
类中既可以有成员变量也可以有成员函数像Stack中的Init、Push、Pop。
struct Stack
{//这里的Stack操作可以直接命名为Init、Push、Pop因为成员函数在类内void Init(){a NULL;top capacity 0;}void Push(int x){}void Pop(){}int* a;int top;int capacity;
};定义了Stack类型下面来定义对象
int main()
{struct Stack st1; //兼容了C语言的定义方式Stack st2; //Stack是一个类类名就是类型return 0;
}此外类相比于结构体的使用还有一个便捷之处
typedef struct ListNode_C
{struct ListNode_C* next; //必须要写struct因为typedef到LTNode;之后才生效int val;
}LTNode;// C
struct ListNode_CPP
{ListNode_CPP* next; //不需要写structListNode_CPP是一个类名ListNode_CPP*是一个指向ListNode_CPP的指针int val;
};3.类的定义格式
class className
{//成员变量成员函数
};C 中虽然可以使用struct来定义类但是C 用户更偏向于使用class来定义类。
4.类的访问限定符和封装
4.1 访问限定符
public公有private私有protected保护
public修饰的成员在类内和类外都能被访问和我们在C语言中使用的struct相似。
protected和private在类外不能访问且在继承中才能体现两者的区别
访问限定符作用域从该访问限定符出现位置到下一个访问限定符若再无访问限定符出现就到结束。
需要特别注意的是class和struct有默认访问权限class默认是私有的struct默认是公有的
class Stack
{void Init(){a 0;top capacity 0;}void Push(int x){}void Pop(){}int* a;int top;int capacity;
};int main()
{Stack st;st.Init();//class默认访问权限私有无法访问return 0;
}在struct中访问权限默认是公有可以直接访问。
4.2 类的两种定义方式
1.声明和定义全放在类中
像我们前面写的Stack类声明和定义都在class Stack{}中完成。
成员函数在类内定义如果符合inline的条件编译器会将其当做内联函数处理。
这里为了方便观察借助一下反汇编 2.声明和定义分离
声明在.h文件中成员函数定义在.cpp文件中
//f.h
struct QueueNode
{QueueNode* next;int val;
};class Queue
{
public:void Init();void Push(int x);void Pop();
private:QueueNode* head;QueueNode* tail;
};
//f.cpp
void Queue::Init()
{head tail nullptr;
}void Queue::Push(int x)
{}void Queue::Pop()
{}在f.cpp中注意限定类域(如果不写类域无法找到对应的声明)inline函数的条件需要满足声明和定义不能分离所以这种定义方式不会出现inline函数。
小函数(汇编指令少)想成为inline直接在类中定义即可 大函数应该声明和定义分离
【面试题】
C中struct和class的区别是什么
解答C兼容了C语言中struct定义结构体的使用另外C中的struct还可以用来定义类这和class定义类是一样的区别在于struct定义的类默认访问权限是public而class定义的类默认访问权限是private。
继承和模板参数列表位置
4.3 封装
面向对象三大特性封装、继承、多态。
封装将数据和操作数据的方法进行结合隐藏对象的属性和实现细节仅仅对外公开接口来和对象交互。
封装本质上是一种管理让用户使用起来更方便。
像可供参观的兵马俑如果不加以保护那么可能慢慢的就没有了参观价值 Stack返回栈顶的元素如果不加以封装数据可能有误
cout st.a[st.top] endl;
cout st.a[st.top-1] endl;栈顶元素的下标是top还是top-1是不清楚的如果直接访问数据可能是错的所以封装一个Top()就显得格外的重要。
int Top()
{return a[top - 1];//设计者来写一定是对的
}这样以来还能保证数据的正确性。
5.类的作用域
类定义了一个新的作用域在类外定义成员的时候需要使用::作用域限定符。
class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout _name _gender _age endl;
}6.类的实例化
前面我们提到类名就是一个类型但是类是抽象的就像盖房子类就相当于图纸图纸是没有空间的。
要想让我们的类有意义就需要实例化对象
假设Person是一个类名下面对其进行实例化
int main()
{Person p1;//实例出的对象是需要占用空间的Person p2;return 0;
}7.类对象模型
7.1 类对象的存储方式
以Person类为例
class Person
{
public:void SetPersonInfo(char* name){_name name;}void PrintPersonInfo(){cout _name _gender _age endl;}
private:char _name[20];char _gender[3];int _age;
};实际上类对象的存储模型可以用一张图来解释 各对象的成员变量存储在各自的地址空间中成员函数则是放在了公共代码区编译链接时根据函数名去公共代码区找到函数的地址。
int main()
{Person p;p.SetPersonInfo(kangkang);//编译链接时找到函数地址p.PrintPersonInfo();
}7.2 类对象的大小
观察下面程序
// 类中既有成员变量又有成员函数
class A1
{
public:void f1() {}
private:int _a;
};
// 类中仅有成员函数
class A2
{
public:void f2() {}
};
// 类中什么都没有---空类
class A3
{};int main()
{//编译环境为MSVC//sizeof(类名)得到的是该类型实体的大小——类对象的大小cout sizeof(A1) endl;//4cout sizeof(A2) endl;//1cout sizeof(A3) endl;//1return 0;
}根据类对象的存储方式得知成员函数是放在公共代码段的并没有在类的内部A2和A3结果之所以为1(编译器不同结果可能出现差异)只是标识对象存在。
成员变量的存储方式遵循结构体内存对齐原则。
7.3 结构体内存对齐原则
第一个成员对齐到结构体偏移量为0的地址处。 其他成员变量要对齐到对齐数的整数倍的地址处。 注意对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8 结构体总大小为最大对齐数的整数倍。 如果出现了嵌套结构体嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是 所有最大对齐数含嵌套结构体的对齐数的整数倍。
【面试题】
1.为什么要进行内存对齐
平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。
性能原因数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问。
2.如何让结构体按照指定的对齐参数进行对齐能否按照3、4、5即任意字节对齐
#pragma pack(8)
//#pragma pack可以修改编译器默认对齐数不能按照任意字节对齐修改对齐数时可能遇到warning C4086: pragma 参数应为 “1”、“2”、“4”、“8” 或者 “16”。
3.什么是大小端如何测试某台机器是大端还是小端有没有遇到过要考虑大小端的场景
大端是将数据的低位存储在高地址处小端是将数据的低位存储在低地址处。
int testPort()
{int a 0x11223344;if (*((char*)(a)) 0x44)return 0;//小端elsereturn 1;//大端
}场景 在不同类型的机器之间通过网络传送二进制数据时机器大小端不同可能导致传输的数据出问题。
8.this指针
假设现在我们有一个日期类
class Date
{
public:void Init(int year, int month, int day){_year year;_month month;_day day;}
private:int _year; // 年int _month; // 月int _day; // 日int a;
};int main()
{Date d1;Date d2;return 0;
}在d1调用Init初始化的时候该函数是如何知道该初始化d1而不是初始化d2的
编译器给每个非静态的成员函数增加了一个隐藏的this指针让该指针指向当前对象。
注意this指针的类型是类名 const this。*
【面试题】
this指针存在哪
解答栈因为this指针是一个形参。
9.类的6个默认成员函数
一个类中什么都没写简称为空类但是空类中真的什么都没有吗
并不是类中会自动生成6个默认的成员函数。 10.构造函数
现假设一个栈的实现是使用一个指针指向一片空间其他成员变量暂不考虑然后对栈进行操作如果栈并没有初始化那么指针就是一个野指针如果不初始化就对栈进行操作很有可能导致程序的崩溃。
既然我们可能会忘记进行初始化工作那么有没有一种办法可以一定进行初始化呢
C新增了非常牛的构造函数就是为了解决这一问题。
typedef int DataType;
class Stack
{
public:Stack(int capacity 4){cout Stack(int capacity 4) endl;_array (DataType*)malloc(sizeof(DataType) * capacity);if (NULL _array){perror(malloc申请空间失败!!!);return;}_size 0;_capacity capacity;}
private:DataType* _array;int _capacity;int _size;
};class MyQueue
{
private:Stack _st1;Stack _st2;
};int main()
{MyQueue q;Stack st;return 0;
}C规定默认构造函数对内置类型不做处理对自定义类型会调用它的构造函数。
队列的实现可以借助两个栈那我们写栈的构造函数不写队列的构造函数那么编译器生成队列的默认构造会调用栈的默认构造函数。
默认构造函数由三种
编译器自己生成的 全缺省的构造函数 无参构造函数
注意一个类中不写构造函数的话编译器会自动生成写了构造函数编译器就不会再自动生成。
C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时
可以给默认值。
class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};像这个程序就用到了C11中的补丁
Date类没有写构造函数编译器会自动生成 自动生成的构造函数会使用Date类中给的缺省值进行初始化 自定义类型调用它的构造函数
总结一般的类都不会让编译器默认生成构造函数都会自己写只有特殊情况才会使用默认生成。
11.析构函数
11.1 概念
通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没的呢
析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成
的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。
11.2 特性
一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数 对象生命周期结束时C编译系统系统自动调用析构函数
class Time
{
public:~Time(){cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:int _year 1970;int _month 1;int _day 1;Time _t;
};Date类没有写析构函数编译器会自动生成一个析构函数自动生成的析构函数在遇到自定义类型时会调用它的析构函数。
在销毁对象时内置类型不需要进行资源清理 Date类默认生成的析构函数会调用Time类的析构函数。
总结如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类但是有
资源申请时一定要写否则会造成资源泄漏比如Stack类。
12.拷贝构造函数
12.1 概念
在现实生活中可能存在一个与你一样的自己我们称其为双胞胎。
那在创建对象时可否创建一个与已存在对象一模一样的新对象呢
拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般使用const修饰)。
12.2 特征
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须是类对象的引用 使用传值方式编译器直接报错会引发无穷的递归调用。 class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}Date(const Date d){_year d._year;_month d._month;_day d._day;}
private:int _year;int _month;int _day;
};3.若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按字节序完成拷贝这种拷贝叫做浅拷贝/值拷贝。
注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。
现假设有一个struct结构体Stack它内部有一个成员变量是一个指针类型创建两个Stack对象st1和st2。 那么st1指向的是一片动态开辟的空间而编译器默认生成的拷贝构造会进行值拷贝也就是说st2中的a也指向这片空间所以在Stack完成资源清理的时候会将同一片空间释放两次导致程序崩溃。 可以很明显的看出同一片空间释放了两次所以默认生成的拷贝构造函数还是不够的。
13.赋值运算符重载
13.1 运算符重载
C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类****型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。
函数名字为关键字operator 后面接需要重载的运算符符号。
函数原型返回值类型 operator操作符(参数列表)
注意
不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型参数用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this. :: sizeof ?: .* 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}bool operator(const Date d2){return _year d2._year_month d2._month _day d2._day;}
private:int _year;int _month;int _day;
};13.2 赋值运算符重载
赋值运算符重载的形式
参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this 要复合连续赋值的含义
Date operator(const Date d){if(this ! d){_year d._year;_month d._month;_day d._day;}return *this;}注意赋值运算符只能重载成类的成员函数不能重载成全局函数。
// 赋值运算符重载成全局函数注意重载成全局函数时没有this指针了需要给两个参数
Date operator(Date left, const Date right)
{if (left ! right){left._year right._year;left._month right._month;left._day right._day;}return left;
}原因赋值运算符如果不显式实现编译器会生成一个默认的;此时用户再在类外自己实现一个全局的 赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的 成员函数。 如果我们并没有显示的去写赋值运算符重载编译器会自动生成一个以值的方式逐字节的拷贝内置类型直接赋值自定义类型会调用对应的赋值运算符来完成。
14.取地址和const取地址运算符重载
这两个默认成员函数编译器会自动生成一般不需要我们去写。
class Date
{
public:Date* operator(){return this;}const Date* operator()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};这两个成员函数一般默认的就可以使用只有特殊情况才需要我们去写——比如想让别人获取指定的内容。
15.初始化列表
初始化列表以一个冒号开始接着是一个以逗号分隔的数据成员列表每个成员变量后面跟一个放在括号中的初始值或表达式。
下面举个栗子
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year 0;//缺省值int _month;int _day;
};Date类中的成员变量使用初始化列表还是在构造函数体内进行初始化都是可以的但是有三种成员变量只能使用初始化列表来初始化引用成员变量、const成员变量 、自定义类型成员(且该类没有默认构造函数时)否则是无法通过编译的。 15.1 引用成员变量
class Date
{
public:Date(int x): ref(x){}
private:int ref;
};15.2 const成员变量
class Date
{
public:Date(int year, int num): _year(year), N(num){}
private:int _year;const int N;
};15.3 自定义类型成员(且该类没有默认构造函数时)
class Time
{
public:Time(int hour){_hour hour;}
private:int _hour;
};class Date
{
public:Date(int year, int hour){_year year;Time t(hour);_t t;}
private:int _year;Time _t;
};16.explicit关键字
这里同样以Date类为例并且有单参数的构造函数
class Date
{
public:Date(int year):_year(year){cout Date(int year) endl;}
private:int _year;
};那我们创建类对象的方式可以这样写
int main()
{Date d1 2022;return 0;
}2022会发生隐式类型转换然后生成一个Date类的临时对象之后通过拷贝构造创建d1。
如果给Date类的单参数构造函数加上explicit关键字那么就无法发生隐式类型转换像以上这种写法就会报错。
explicit Date(int year):_year(year)
{cout Date(int year) endl;
}17.static成员
声明为static的类成员称为类的静态成员用static修饰的成员变量称之为静态成员变量用static修饰的成员函数称之为静态成员函数。
注意静态成员变量一定要在类外进行初始化。
【面试题】
实现一个类计算程序中创建出了多少个类对象。
实现
class A
{
public:A() { _scount; }A(const A t) { _scount; }~A() { --_scount; }static int GetACount() { return _scount; }
private:static int _scount;
};
int A::_scount 0;静态的_scount任何类对象都可以访问调用构造函数和拷贝构造函数的时候_scount析构的时候
–_scount就可以了。
需要注意的是静态成员函数没有隐藏的this指针不能访问任何非静态成员。
下面给class A增加上成员变量nostatic并在static成员函数中访问
class A
{
public:A() { _scount; }A(const A t) { _scount; }~A() { --_scount; }static int GetACount() { nostatic 0;return _scount; }
private:static int _scount;int nostatic;
};没有this指针这种访问是无法进行的。
C类和对象的大部分内容在本篇中都有提到可能还有一小部分知识点没有涉及到没有涉及到的知识点大家可以私信我我后续也会对内容做一些补充尽可能抽出时间来慢慢完成。