c++基础知识
面向对象语言三大特征:
封装,多态,继承
封装:
1、将函数定义到结构体内部,就是封装.
2、编译器会自动传递结构体的指针给函数.
类:
带有函数的结构体,称为类.
成员函数:
结构体里面的函数,称为成员函数.
this指针:
c++中默认传递一个对象首地址,这个地址就是this指针
thie指针特点
1、你用或者不用,它就在那里
2、参数个数确定的时候,用ecx来传递
3、参数个数不确定的时候,最后一个传递(参见不定长参数)
4、this指针不能做++ -- 等运算,不能重新被赋值.
5、this指针不占用结构体的宽度.
构造函数的特点:
1、与类同名
2、没有返回值(指的是前面连void都不能有)
3、创建对象的时候执行
4、主要用于初始化
5、可以有多个(最好有一个无参的),称为重载 其他函数也可以重载
6、编译器不要求必须提供
析构函数的特点:
1、只能有一个析构函数,不能重载
2、不能带任何参数
3、不能带返回值
4、主要用于清理工作
5、编译器不要求必须提供
6. 不需要自己调用该函数,编译器自己帮我们最后执行
在函数名前面加一个~,表示是析构函数
继承
1、什么是继承?
继承就是数据的复制
2、为什么要用继承?
减少重复代码的编写
如果是Teacher、Student继承了Person
3、Person 称为父类或者基类
4、Teacher、Student称为子类或者派生类
Student s Person t
5、t和s可以称为对象或者实例.
6、可以用父类指针指向子类的对象.(绝对安全)
多层继承:允许多重继承,即子—>父—->爷(s->t->v)
如果子和父中有相同变量(a) 使用 s::a t::a 来告诉编译器这个a到底是谁的参数
多重继承:(微软不推荐,给编译器工作增大)
即有多个父类
struct Z:X,Y
权限控制
将定义与实现分开:
将定义与实现分离,代码会有更好的可读性
可以在头文件中 写上类中变量,函数申明
在.cpp文件中写上函数具体实现 xx::FunctionName 表示这个函数名是xx类里面的
1、xxx.h 只是一个文件,可以是任何的后缀名,如果你愿意,
2、#include 的作用只是把里面的内容复制过来 仅此而已.
如:#include "abc.exe"
3、xxx.h 与 xxx.cpp并不要求一定同名
public private的使用
public的意思是,这个成员哪里都可以用,不用担心被修改,所以,一旦发布成public的成员,是不能(不应该)够改名字的.
private的意思是,这个成员只用于(类的)内部使用,不要在其他的地方使用.
1、对外提供的函数或者变量,发布成public的 但不能随意改动.
2、可能会变动的函数或者变量,定义成private的 这样编译器会在使用的时候做检测.
3、只有结构体内部的函数才可以访问private的成员.
4、public/private可以修饰函数也可以修饰变量.
private真的不能访问吗?
使用private只是在编译器层面上限制访问 在底层 汇编角度来说 有没有private反汇编代码都是一样的
所以我们使用指针同样可以访问
private修饰的成员与普通的成员没有区别 只是编译器会检测.
private修饰的成员只要自己的其他成员才能访问
class与struct的区别
成员区别
编译器默认class中的成员为private 而struct中的成员为public
继承区别
父类中的程序继承后默认变成private属性
如果不希望改变成员的属性:
class Sub:public Base
填上public,否则默认private
继承的时候会先执行父辈的构造函数
private是否被继承
1、父类中的私有成员是会被继承的
2、只是编译器不允许直接进行访问
用指针访问
虚函数表
1、通过对象调用时,virtual函数与普通函数都是E8 Call
对象调用:
Student p;
p.study();
这就没体现多态嘛,自然就算不上是动态绑定
2、通过指针调用时,virtual函数是FF Call,也就是间接Call
指针调用:
Person* s = new Student(); s->study();
多态,所以动态绑定,运行时才知道要调的是那个函数,所以FFcall,先存个地址
1、当类中有虚函数时,会多一个属性,4个字节
2、多出的属性是一个地址,指向一张表,里面存储了所有虚函数的地址
绑定
绑定就是将函数调用与地址关联起来.
前期绑定/静态绑定:编译完时就已经绑定
后期绑定/动态绑定:在调用时才绑定,有虚表,virtual虚函数
1、只有virtual的函数是动态绑定.
2、动态绑定还有一个名字:多态
重载
一个类中:
两个重载函数必须在下列一个或两个方面有所区别:
1、函数的参数个数不同。
2、函数的参数类型不同或者参数类型顺序不同
举例:
void Breath() { printf("学生应该呼吸新鲜的空气\n"); } void Breath(string whe) { printf("学生应该呼吸whe新鲜的空气\n",whe); }
析构函数重载:
Student() { printf("Student已经初始化\n"); } Student(int grade, int id) { this->grade = grade; this->id = id; }
重写(覆盖)
重写(覆盖):是指派生类(子类)中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类(父类)中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用基类函数。重写的基类中被重写的函数必须有virtual修饰:
1 class Person 2 { 3 private: 4 int age; 5 string name; 6 static int right; 7 public: 8 Person() 9 { 10 printf("person已经初始化\n"); 11 } 12 int GetAge() 13 { 14 return this->age; 15 } 16 void SetAge(int age) 17 { 18 this->age = age; 19 } 20 string GetName() 21 { 22 return this->name; 23 } 24 void SetName(string name) 25 { 26 this->name = name; 27 } 28 void study() 29 { 30 printf("人可以学习!\n"); 31 } 32 void eat() 33 { 34 printf("人可以吃饭\n"); 35 } 36 37 }; 38 39 class Student:public Person 40 { 41 private: 42 int grade; 43 int id; 44 public: 45 Student() 46 { 47 printf("Student已经初始化\n"); 48 } 49 Student(int grade, int id) 50 { 51 this->grade = grade; 52 this->id = id; 53 } 54 int GetGrade() 55 { 56 return this->grade; 57 } 58 int SetGrade(int grade) 59 { 60 this->grade = grade; 61 } 62 int GetId() 63 { 64 return this->id; 65 } 66 void SetId(int id) 67 { 68 this->id = id; 69 } 70 void Breath() 71 { 72 printf("学生应该呼吸新鲜的空气\n"); 73 } 74 void study() 75 { 76 printf("学生是学习的大好时刻\n"); 77 } 78 }; 79 int main() 80 { 81 Person* s = new Student(); 82 s->study(); 83 84 }
这里先没用virtual修饰:
多态就没有得到体现,要重写就必须要加上virtual
virtual void study() { printf("人可以学习!\n"); }
纯虚函数
有virtual修饰的就是虚函数,虚函数意义在于可能要重写,但这是可能。假设我们希望描述大雁和老虎的行为,定义一个父类"animal",但我们无法直接描述,比如大雁是"飞翔",老虎是”奔跑“,那么我们如果定义父类时就没法明确的写出他们的行为到底是什么,因此有了纯虚函数:"我知道子类要继承使用这个方法,但我没法具体给出",纯虚函数是强制重写,不给出方法体,子类自己去实现。
实现方式:
virtual void study() = 0;
多态:
1.动态绑定
2.体现出了不同的行为
模板:
使用template<class T>来声明一个模板
T是未确定的类型,让我们可以使用不同的类型
引用类型
我们日常使用指针时,有可能一些意外的操作,将本不该指向的地址作为了指针的值,这样有不安全的风险,使用"引用" int& x; 就是讲指针中的* 改为& ,这样设计的好处在于:编译器不让你改这个指针的地址,增强了安全性
但从底层来说,汇编代码没有区别,实际上就是编译器限制你修改
使用x = 10; 来直接给x这个地址中的值赋值 ,而不是修改地址
1、引用类型是C++里面的类型
2、引用类型只能赋值一次,不能重新赋值
3、引用只是变量的一个别名,使用的时候按照变量去使用
4、引用可以理解成是编译器维护的一个指针,但并不占用空间
5、使用引用可以像指针那样去访问、修改对象的内容,但更加安全.
友元函数
friend void Print(const Person& refPer);
在类中函数前添加一个friend,表示该函数为该类的朋友
这样这个函数也可以访问类的私有成员
(也可以定义一个友元类)
什么情况下需要友元函数:
(1) 运算符重载的某些场合需要使用友元.
(2) 两个类要共享数据的时候.
友元函数和类的成员函数的区别:
(1) 成员函数有this指针,而友元函数没有this指针
(2) 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友
运算符重载:
有时需要一些针对特定类型进行操作的运算符
Number operator+(const Number& p);
1、运算符重载就是函数替换
2、. :: ?: sizeof # 不能重载
new delete
是c++中的在堆中分配空间,释放空间的关键词
在底层,与malloc free没有区别,都是调用的系统API kenel32这个dll中的 heapalloc 和heapfree
在使用时,new int[10] 对应的是delete[] 方括号里不需要写值
否则只会释放数组中的一个单元,本质delete是用一个循环free
Vector(动态数组)
1、本质就是一个数组
2、可以动态扩充容量
3、支持下标方法,查询性能好
4、新增数据和删除数据较差
链表:
特点:
1、数据分散存储
2、查询性能没有Vector好
3、新增与删除的性能好于Vector
有单向链表,循环链表,双向链表
命名空间
主要是为了彻底解决相同名字冲突的问题主要是,class重名
创建一个命名空间
namespace x
{
int y = 0;
void func();
class Test
{};
}
里面可以有类,函数,全局变量,当已经定义在命名空间后,使用必须加上命名空间名称,如x::func()
或者使用using namespace x;
static关键字
在面向过程中(c)
如果声明一个static的局部变量,那么这个局部变量变成全局变量,并且只能该函数访问,为私有的全局变量
在函数或变量前声明static
static void add(int x,int y);
static int x;
那么别的文件就无法访问这个函数或者这个变量
在面向对象中(c++)
用static声明一个变量就决定了这个变量是一个全局变量
并且只能由当前类中的函数使用,但这个变量已经不在类中了
同全局变量相比,使用静态数据成员有两个优势:
1.避免命名冲突;
2.可以实现信息隐藏;
静态成员函数
仅仅是为了方便
可以定义一个静态函数在一个类中,当我们想访问的时候就可以直接通过:类名::函数名() 调用,无需创建对象来调用
static关键词的经典应用:单子模式
有些时候我们希望定义的某些类只能有一个对象存在(因为一个对象就已经足够了),该如何进行限制呢?
实现思路:
1、禁止对象随便被创建
2、保证对象只有一份存在