开始
-
C++程序中必须包含一个main函数
-
cerr:标准错误 -
某个变量在不同cpp文件中都有,为了避免混乱,但使用时一般需要声明该变量
extern -
类型别名
typedef,typedef int wa//wa是int的同义词 -
一般是.h文件的固定开头.以下为例,在别处引用时,#include SALE_DATA_H.h
#ifndef SALE_DATA_H #define SALE_DATA_H #endif -
空指针:nullptr int* ptr = nullptr; //此时ptr是空指针
-
string表示可变长的字符序列,vector存放的是给定类型的可变长的序列
-
关于string:" "(双引号)
-
#include <string> using namespace std; -
定义和初始化
string s1; string s2(s1); string s3("value"); string s3 = "value"; string s4(n, 'c'); //这里注意是单引号 -
操作
#include <string> using namespace std; string s; s.push_back('c'); //单引号,也就是只能在后面添加单个字符 s.empty(); //判空 s.size(); //字符个数 s[n]; //第n个字符的调用 s1+s2; //字符的拼接 <,>,<=,>= //比较 判断字符代表的数字的大小,可以判断字符是否相等
-
-
关于vector
-
定义和初始化
vector<T> v1; vector<T> v2(v1); vector<T> v2 = v1; vector<T> v3(n, val); //n个val值组成 vector<T> v4(n); //n个0值组成 vector<T> v5{a,b,c.....} vector<T> v5 = {a,b,c,....} -
操作
v.empty(); v.size(); v.push_back(t); v[n];
-
-
关于迭代器iterator
-
迭代器运算符
*iter //返回引用 iter-> men //等价于(*iter).men ++iter; --iter; iter1 == iter2; iter += n; iter1 - iter2; // 两个迭代器之间的距离
-
-
多维数组
int *ip[4] //含有4个整型指针的数组 int (*ip)[4] //含有4个整型的数组 -
条件运算符
condition ? ture experssion:false experssion -
位运算符
~ //位取反 1置0,0置1 ~expr & //位与 对应位置都是1,则为1,否则为0 ^ //位异或 对应位置有且只有一个为1时,则为1,否则为0 a^a^b=b 刷题中的只有一个元素 | //位或 对应位置至少有1位1,则为1,否则为0 -
指针
-
指针是一个值为内存地址的变量(或数据对象)
-
类似于身份证号,每个身份证号代表地址,指向某个人
-
在计算机中本来是地址-值,但是人类无法记住地址,就将地址转换为变量名
int year = 2018; int *ptr = &year; //将year的地址赋值给ptr指针 -
声明中的*和使用中的 *完全不同!!!!
- 声明中的*,代表当前变量是地址
- 使用中的*,代表指向地址对应的值
int num = 1024; int* ptr_num = # *ptr_num = 1111; //使用时的*代表间接运算符,指向ptr_num指向的1024,并将其修改为1111 cout << num << endl; // num = 1111; -
取地址符&
-
char *的操作!!!!!!!
char ch = 'a'; char* ptr_ch = &ch; cout << ptr_ch <<endl; //此时输出的ptr_ch是乱码,因为char *在c++中默认是字符串 cout <<(void *)ptr_ch << endl; // 此时输出的ptr_ch是地址,因为通过了void *强制转换 -
指针类型
-
空指针:
int* ptr = nullptr;int* ptr=NULL- 指针很危险,如果只声明不定义的话,指针不一定会知道什么地方,所以要定义为空指针。
-
void*指针:一中特殊的指针类型,可以存放任意类型对象的地址
-
注意void*指针不可以直接修改对象的值,也就是
double pi = 3.14; void* ptr_pi = π *ptr_pi = 1122.1; //错误,void*指针无法直接修改对象的值 -
void*指针的用途
- 与别的指针进行比较
- 作为函数的输入和输出
- 赋值给另一个void*指针
-
-
-
-
引用:给对象起了一个别名
int&-
例如:
int& refValue = int_value;,如果int& refValue,错误,引用必须初始化 -
引用只能跟对象绑定,
int& = 10(错误)int& = root(正确)- 如果非要引用常量,
const int& = 10;(正确)
- 如果非要引用常量,
-
实际原理还是指针,在使用时与
*ptr相同
-
-
指针与数组(存储在一块连续的内存空间)
-
指针任何时候都占4个字节空间
-
数组名等于数组的首地址
-
数组的类型:double[]
-
第i+1个元素可表示为
- 地址:
&num[i+1]、num+i - 值:
num[i+1]、*(num+i)
- 地址:
-
数组名不能++,但是指向数组名的指针可以++
int num[] {1,2,3,4}; int* ptr_num = num; cout << *++ptr_num << endl; // 输出为2 cout << *++num << endl; //输出错误,数组名不能++ -
num的值与&num[0]的值相同,其实地址相同
-
-
指针的运算
- 指针的平移(++、–)
- 指针的算术运算(+2,-3)
- 指针可以减(加)去任意数,但是很危险,不知道指到哪里了
- 尤其是一些for循环的时候,很危险,不知道减到哪
-
动态分配内存
-
使用new分配内存:当运行阶段时才分配内存
int num1; //在编译阶段就要分配内存 int* ptr_int = new int; //在运行阶段分配内存 //上面这句话的意思是在运行阶段分配一个未命名的大小为int值的内存,并使用ptr_int指向该内存 //该空间只能由指针访问 delete ptr_int; //释放由new分配的内存,与new成对出现 -
delete释放
- 与new成对出现
- 不要释放已经释放的内存
- 不要释放普通内存
- 释放变量:
delete num;释放数组:delete [] nums(int * nums = new int[10])
-
程序的内存分配
- 栈区:由编译器自动分配释放,一般存放函数的参数值等
- 堆区:有程序员分配释放
- 全局区(静态区-static):全局变量
- 文字常量区
- 程序代码区

-
-
二维数组与指针
-
首地址:
&a[0][0] -
使用指针创建二维数组
int* p = new int[10]; //使用一个指针创建一维数组 int (*p)[3] = new int[5][3]; //使用一维指针创建二维数组,每个指针代表一行,变相的降维操作 //也就是说指针的数量与行数相同 //每个指针代表二维数组中每行的首元素的地址 //使用指针访问二维数组 for(int i = 0, i < 5; i++){ for(int j = 0, j < 3; j++){ cout << *(*(p+i)+j) <<endl; } } //*(p+i) 代表选定某行 *(*(p+i)+j) 代表选定某个元素- 在二维数组array里面,
*(array+i)等于的是某行首元素的地址而非值- 所以指针访问时,
*(*(array+i)+j)才等于array[i] [j]的值
- 所以指针访问时,
- 在一维数组nums里面,
*(nums+i)等于第i+1个元素的值
- 在二维数组array里面,
-
直接创建二维数组
int main() { int a[2][3] = { 0 }; for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { if (i == 1 && j == 1) { a[i][j] = 3; } else { a[i][j] = j; } } } cout << "*a[0]:" << *(a[1]+2) << endl; }*a[0]— 代表取第一行首位置的值 a[0] 代表第一行首位置的地址*(a[0]+1)— 代表取第一行第二个位置的值a[0] [1]a[0][1]代表取第一行第二个位置的值 *a[0] [1] 的语法错误,需要小心!!!*a[0]++— 代表首先取a[0] [0]的位置,之后将指针指向到a[0] [1]的地址 —— *a[0] a[0] += 1
-
-
函数
-
函数分类:STL(重要)和boost C++
-
函数“三要素”:返回值类型(void没有返回值),函数名,参数列表
-
自定义函数的完整写法:函数原型(先告诉编译器,函数的返回值和参数列表,观察是否合法),函数定义,函数调用
int sum(int, int); //函数原型 int main(){ int result = sum(5, 3); //函数调用 } //函数定义 int sum(int num1, int num2){ //函数实现代码 } -
函数中不能返回数组
-
-
函数中的参数和按值传递
-
在给函数传递参数时,参数值不会直接传递给函数而是将参数值复制一份副本传递给函数,也就是说函数是对该副本进行操作,对原来的参数值没有影响
void change(int); void change(int num){ num++; } void change1(int &num1){ num1++; } int main(){ int num = 9; change(num); //传入的是num的副本,而不是num,输入为9 change1(num); //此时相当于是int & num1 = num,此时num1是num的引用,都指向num的值, //因此输出为10 } -
使用数组进行函数参数传递
void input(int []); void input(int values[], int len){ // 因为传入数组时,实际传入的是数组的指针,并不知道长度,所以一般还需要输入数组的长度 } //当传入的数组不想被修改时,在之前加上const void input(const int [], int); //在定义的时候,也需要加上const void input(const int values[], int len) //在调用的时候不需要加上const,因为此时已经定义好不被修改了 input(int valus[], int) -
二维数组做参数
void show(double (*)[5], int); //函数声明中只写二维数组的形式,不写具体的名称 void show(double (*arr)[5], int len){ //函数实现 } -
函数指针:函数地址,存储机器语言代码的内存的开始
double sum(double, double); double (*ptrSum)(double, double); //函数指针声明 ptrSum = sum; //将函数指针指向函数 ptrSum(3,2); //这样就实现了调用 double *ptrSum(double, double); //当不叫括号时,代表声明ptrSum函数,返回值为double* -
函数指针的一般调用方法:
#include <iostream> using namespace std; void print_result(double (*)(double, double), double, double); void print_result(double (*ptrCalc)(double, double), double num1, double num2){ double result = ptrCalc(num1, num2); cout << result << endl; } double sum(double, double); double sum(double num1, double num2){ return num1+num2; } int main{ //每次定义函数指针很麻烦,可以使用auto自动推断类型 //auto ptrC = addtion; // auto一定要初始化,只有初始化后才能获得auto对应的类型; //typedef double(*ptrC)(double, double); 定义了ptrC类型,此时ptrC是一种数据类型而不是函数指针 //ptrC ptrC1; double(*ptrC)(double, double); //定义一个名为ptrC的函数指针 cout << "输入两个操作数:" << endl; cin >> num1 >> num2; ptrC = sum; //将函数指针指向sum函数 print_result(ptrC, num1, num2); //输入的第一个参数时函数指针,类型不能变换 return 0; }
-
-
内联函数:inline,为了提高程序的运行速度。形式:inline func()(声明和定义,任选其一)
- 本质:替换;将内联函数复制过去执行,代价是内存占据比较多。
- 使用时机:当代码比较简短的时候,可以使用。但当代码比较长的时候,没有必要使用内联。
- 替换:
#define N 5将N替换为5#define s(num) num*nums(5) = 25;
-
引用回顾
int& num = valuevoid Swap(int &, int &) void Swap(int& num1, int & num2)-
引用常量是函数参数时,函数对值进行操作,而非副本
-
使用引用的理由
- 可以更加简便的书写代码
- 可以直接传递某个对象,而不是赋值一份
-
由于引用是直接对本体进行操作,但是有时候我们并不希望修改函数,所以需要在函数的参数前加const(声明和定义中都要)
-
在使用引用时,强烈建议加上const
-
-
返回引用类型
-
不要返回局部变量的引用:因为函数中的局部变量在函数运行完成后会释放局部变量,随着代码增多,再进行函数局部变量的引用们就会出现函数局部变量发生改变。
int &sum(){ int num = 10; int &rNum = num; return rNum; //后续随着内存紧张,局部变量的地址会被占用 } -
引用函数可以不返回值,默认返回传入的引用对象本身,且是函数中最后参与计算的引用
- 不过一般不这样使用,因为错误很多
int& sum(int& num1, int& num2){ num1++; num2++; //没有返回值 } int& sum1(int& num1, int& num2){ num2++; num1++; //没有返回值 } int num1 = 10; int num2 = 15; int& result = sum(num1, num2); // result = 16 int& result1 = sum1(num1, num2); // result1 = 11 -
返回引用时,要求函数参数中包含被返回的引用对象,也就是函数参数中必须有该返回对象的名称,不能在函数中新建一个引用再返回
int& sum(int& num1, int& num2){ return num1+num2; //错误,num1+num2代表一个新的引用,不可以 return num1; //可以,因为函数的参数列表中包含引用num1 } -
在返回引用类型时,最好在函数前加上const
const int& sum(){} //形式
-
-
默认参数
void sample(int = 10)//给定了int空间的一个10-
默认值可以在函数原型或定义中给出,不能同时给出,任选其一
-
对于带参数列表的函数,必须从右向左添加默认值
void test1(int a, int b =5, int c =6); //合法 void test2(int a, int b =5, int c); //不合法,因为必须从int c开始赋值
-
-
函数模板:实际上是建立一个通用函数
-
函数定义时不指定具体的数据类型(使用虚拟类型代替)
-
函数调用时根据实参,反推数据类型-类型的参数化
-
形式
temple <typename 类型参数1 typename 类型参数2,....> 返回值类型 函数名(形参列表){ //函数体 }
-
面向对象
-
以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建响应的软件系统(模拟现实)
- 对象是由数据和容许的操作组成的封装体
-
c++中的类:将抽象转换为用户定义类型的工具
-
类的实例成为对象
-
类中的变量和函数称为成员
-
类的声明:class/struct关键字声明类型
- class默认私有类型(private,书写函数等),struct默认共有类型(public,一般只写数据)
- 无法在类的外部访问私有变量
-
在·
.h文件的类中,一般声明类的成员函数,之后在.cpp文件中定义成员函数.特别的,当在.cpp文件中定义成员函数时,要注意所属关系,形式:返回值 类名::成员函数(参数列表)class num(){ public: void values(int); //函数声明 } void num::values(int nums){ //函数定义 //函数体 }
-
-
常见的访问修饰符
- public:任何地方都可以访问
- private:只能在类中或者友元函数中访问,一般形式为在前面加上下划线
_value- 当想访问私有变量时,可以通过在类中设置成员函数的方法进行访问。
- protected:在类中、子类和友元函数中访问
-
构造函数
- 类名作为函数名
- 无返回值
class LandOwner{ private: int userld; public: LandOwner(){//构造函数 userld = 1000; }; -
作用
- 初始化对象的数据成员
- 类对象被创建时,编译器为对象分配内存空间并自动调用构造函数以完成成员的初始化
-
构造函数的种类:
- 无参构造与带参构造
- 函数重载机制相同
- 一般构造(重载构造)
- 拷贝构造
- 无参构造与带参构造
-
栈内存(小仓库,速度快;去饭馆)和堆内存(大仓库,速度慢;自己做饭)
-
析构函数:鼓励使用堆内存,不要使用栈内存
- 对象过期时自动调用的特殊成员函数
- 一般完成清理工作
- 释放对象使用的资源,并销毁非static数据成员
- 无论何时一个对象被销毁,都会自动调用其析构函数。
- 析构函数没有参数,只能有一个
class Student{ private: double* scores; public: Student(int len) { scores = new double[len]; } ~Student(){ delete scores; //释放空间,非常重要。如果没有使用new的话,栈空间也会自动释放内存 } } -
使用类创建对象
- vector.at(i) //取第i个位置的元素
-
this指针(还是没有多大的难度)
-
每个成员函数中农都有this,只能在类中使用
-
可以通过this访问当前对象的成员
this->成员名; this->函数名; -
this返回当前对象的引用
return *this,涉及到引用- 一般在函数的末尾添加const,使得this指针不变,既无法改变函数内部
Student& GetSuperSchloar(Student& ) const //函数声明,此时this指针改为静态无法对对象内部进行操作 { } Student& Student::GetSuperSchloar(Student& stu) const { //this->GetName("test"); //错误,此时this指针不能改变对象内部的成员 return *this }
使用类
-
运算符重载:
operator 运算符()-
想法转换:简化函数调用方式
-
使用运算符重载可以对两个对象相加
-
详解const:不能修改任何成员(函数和变量),成员函数不可以调用,成员变量可以调用
-
const修饰成员变量
-
const位于*的左侧:表示指针所指的数据是常量,不能通过该指针修改实际数据,指针本身是变量,可以指向其他地址
const int* ptr1_num1 = &num1; 此时指针可以修改,指向的数据不可以修改 int const *ptr1_num1与上面等价 -
const位于*的右侧,指针是常量,而指针指向的数据可以修改
int* const ptr3_num3 = &num1; 此时指针ptr3_num3是常量,对应的值可以修改 -
*的左右两侧都是const,则指针和指针所指向的值都不可以修改
-
-
const修饰函数参数
void ConstTest2(const int num){ num = 123; //不合法,此时num是常量,无法修改 }-
const修饰引用,不能修改引用对象的任何成员(成员函数和成员变量),并且成员函数无法被调用、成员变量可以被调用
- 好处:保护传递的参数;不需要新的参数副本;使用const传递对象的引用时可以起到不copy的作用
class Computer { private: int m_core; public: Computer(int core) {this->m_core = core;} void SetCore(int core) {this->m_core = core;} void buy() {}; void buy(int core) { } } void ConstTest3(const Computer& computer) { computer.buy(123); //不合法,不能修改const引用对象。虽然buy中没有操作,但哪怕是简单的调用都不合法 computer.buy(); //仍然不合法,调用也不可以 //如果引用对象添加const之后什么也无法操作,那么就没有意义啦,所以要想调用const引用对象的成员函数时,需在成员函数前添加const void buy() const{}; computer.buy(); //合法 }
-
-
const修饰返回值
-
使用const修饰引用类型的一个常见原因是提高效率
-
如果函数返回局部变量,就直接返回这个对象不要返回引用
-
因为局部变量在运行函数结束后就会被释放,那么该局部变量的引用地址也同样会被销毁,这时直接返回局部变量即可
const Comput GetMax(const Computer& com1, const Computer& com2) { Computer com3; if(com1.GetCore() > com2.GetCore()) com3 = com1; else com3 = com2; return com3; //此时返回com3本身,而不要返回com3的引用 } -
在可以返回对象,也可以返回引用时,首选引用
-
const Comput& GetMax(const Computer& com1, const Computer& com2) { //返回的是对某个对象的引用 //当存在某个对象及其引用时,一般返回引用 } -
-
const修饰函数,说明函数不会修改成员变量的值
class TestClass { public: int value; void ModifyValue() const { value = 1111; //不合法,后面加上了const修饰函数,则该函数不能修改成员变量的值 } }; const integer integer::operator+(const integer& other) const { integer result(this->IntValue() + other.m_value); //不合法,此时最后一个const修饰函数,则该函 数对应的this指针只能调用成员变量,无法调用成 员函数 integer result(this->m_value + other.m_value); //合法 return result; }
-
-
可以重载的运算符

-
-
友元函数:主要是用来做重载,重载范围如下所示,注意流运算符<<、>>

- 重要应用使用友元函数重载流运算符<<,ostrem是一种输出的类型.通过这种方式可以直接输出对象的类型
friend ostrean& operator<<(ostream& out, const integer& num) { out << num.m_value; return out; } cout << "重载<<后,可以直接输出对象的格式" << int5 << endl; //直接输出int5.m_value,更加方便 friend istream& operator>>(istream&, integer&); istream& operator>>(istream& in, integer& num) { in >> num.m_value; return in; } -
复制构造函数
-
自定义string类,以简化字符串操作
-
复制构造函数的定义
class String { public: String(); String(char* str); //如果构造函数中的参数是自己类型的引用,则称为该构造为复制构造或者拷贝构造 String(const String & str); }; -
使用复制构造函数的场景
- 当类的对象被初始化为同一类的另一个对象时
String(const String& str); - 当对象被作为参数传递给一个函数时
- 当函数返回一个对象时
- 当类的对象被初始化为同一类的另一个对象时
-
当重载赋值运算符时,一定要保证将一个数据中的所有对象都赋值过去(特别是由指针存在时)!!!
-
这是因为如果直接用等号将两个数据对象赋值时,很可能是将数组指针赋值过去了
-
如果包含多个对象,则每个成员都需要赋值到内存对象中—深复制
-
如果一个类拥有指针类型的成员,那么大部分情况下都需要深拷贝,因为很容易此时赋值的是指针。如果类没有指针类型的成员,则浅复制即可。
String str1 = "abcd" ; //简化写法 String str2 = "中文"; str1 = str2; //此时只是将str2的数组地址赋值给str1了,换句话说现在str1和str2指向了同一地址,但是 我们只想将内容赋值,所以需要对赋值符号进行重载重载如下所示。
-
class String { public: String(); ~String(); const String& operator=(const String&); private: int m_length; char* m_value; //char*默认是字符串 } String::String() : m_length(0) //默认长度是0,但是实际中仍然会有"\0" { m_value = new char[1]; m_value[0] = "\0"; } String::String(char* str) { if(NULL == str){ m_value = new char[1]; m_value[0] = "\0"; } m_length = strlen(str); //测量要赋值的字符串的长度 m_value = new char[m_length + 1]; strcpy(m_value, str); } const String& String::operator=(const String& str) { delete[] m_value; //释放原字符串的空间 m_length = strlen(str.m_value); m_value = new char[m_length + 1]; strcpy(m_value, str); return *this; } int main() { String str1("abc"); String Str2("中国"); str1 = str2; //合法,此时只有内容赋值过去,地址并没有改变 } -
-
模板函数
-
模板函数一般用来做算法,比如重载100次某个函数
-
有模板函数参与时,需要将类的构造和定义放在同一文件
-
形式如下。当后续类定义时需要在类后加,形如MyVector。在调用该类时,需要将T的类型写出,如MyVector。
template<typename T> //后面紧跟类名 class MyVector { public: MyVector(); MyVector(int len, T element); MyVector(const MyVector<T>& vec); MyVector<T>& operator=(const MyVector<T>& vec); T& operator[](int index); template<typename T2> friend ostream& operator<<(ostream& out, const MyVector<T2>& vec); void push_back(T element); ~MyVector(); private: T * m_elements; //存放元素数组 int m_length; //实际数组内元素的数量 int m_capacity; //实际数组所占的空间 } template<typename T> MyVector<T>::MyVector() : m_capacity(16),m_length(0) { m_elements = new T[m_capacity]; } template<typename T> MyVector<T>::MyVector(int len, T element) { m_length = len; m_capacity = len + m_capacity; m_elements = new T[m_capacity]; for(int i = 0; i< m_length; i++){ m_elements[i] = element; //memcpy(&m_elements[i], &element, sizeof(T)) } } template<typename T> MyVector<T>::MyVector(const MyVector<T>& vec) { m_length = vec.m_length; m_capacity = vec.m_capacity; m_elements = new T[m_capacity]; memcpy(m_elements, vec.m_elements, sizeof(T)*m_length); } template<typename T> MyVector<T>& MyVector<T>::operator=(const MyVector<T>& vec) { if(this == &&vec) return *this; if(NULL != m_elements) { delete[] m_elements; m_elements = NULL; } m_length = vec.m_length; m_capacity = vec.m_capacity; m_elements = new T[m_capacity]; memcpy(m_elements, vec.m_elements, sizeof(T)*m_length); return *this; } template<typename T> T& MyVector<T>::operator[](int index) { return m_elements[index]; } template<typename T> void MyVector<T>::push_back(T element) { if(NULL == m_element) { m_capapcity = 16; m_length = 0; m_elements = new T[m_capacity]; } if(m_length == m_capacity) { T* newElelments = new T[m_capacity*2+1]; memcpy(newElements, m_elements, m_length*sizeof(T)); delete[] m_elements; m_elements = newElements; } this->m_elements[m_length++] = element; } template<typename T2> ostream& operator<<(ostream& out, const MyVector<T2>& vec) { for(int i = 0; i < m_length; i++) { out << m_elements[i] << ", "; } out << endl; return out; } template<typename T> MyVector<T>::~MyVector() { delete[] m_elements; }
-
-
类型转换
-
强制类型转换
int num = (int)99.9; -
转换构造函数:构造函数中只有一个函数时称为转换构造函数,将其他类型转换为当前类型使用
- 例如:
rect6 = rect5 + 'A' + false- 将
'A'和false转换成rect类型(调用转换构造),再进行加法运算符重载,实际上相当于rect6 = rect5 + rect('A') + rect(false)的运算,所以转换构造比较重要
- 将
- 几种构造函数的合并:
Rectangle(float width, float height) : width(width),height(height)
- 例如:
-
类型转换函数;
-
语法格式,只能在类中定义
operator type() { return data; } class Rectangle { operator float() const{ return width } } Rectangle rect1(55); cout << float(rect1) << endl; //输出为55
-
-
-
.h文件中的预处理命令的作用
-
形式
#ifndef XX_H #define XX_H #endif
-
-
继承和派生
-
继承定义:在已经存在的类的基础上,再建立一个新类
-
派生定义:从已有的类派生出新的类,派生类继承了原有类的特征,包括成员和方法
-
继承
-
基类(父类)定义了公共的内容,方便统一修改(派生类:子类)
- 派生类对象继承了基类的数据成员(包含数值)和公有函数
- 如果不修改的话,默认是父类的值
- 派生类修改,则就调用派生类修改后的成员
- 派生类对象可以使用基类的非私有函数
- 派生类需要自己的构造函数
- 派生类可以根据需要添加额外的数据成员和函数
- 派生类对象继承了基类的数据成员(包含数值)和公有函数
-
创建派生类对象时,首先调用基类构造,之后调用派生类构造
-
基类与派生类的特殊关系

-
const
- 在前,返回值是一个常量,也就是返回值是只读,在其他地方也不能修改
- 在后,表示方法不可以修改成员的值,尽量使用引用
-
存储方式
-
没有继承时的类存储
- 编译器会将成员变量和成员函数分开存储:分别为每个对象的成员变量分配内存,但是所有对象都共享同一段函数代码

- 类的大小是成员变量的大小
-
有继承关系

-
-
-
-
多参构造的小技巧:首先设定一个全参数设置的构造函数,之后根据需要修改其中的某个参数
class Hero { //省略 private: string _nickName; int _level; int _curLift; } Hero::Hero() : Hero("默认英雄", 1, 100) { } Hero::Hero(const string& name) : Hero(name, 1 ,100) { } Hero::Hero(const string name, int level, int cur_lift) : _nickName(name), _level(level), _curLift(cur_lift) { } -
派生类成员的访问控制
- 继承方式提供给程序员对类进行封装的机制
- 全部继承,不封装基类,那么用公有继承
- 鸟是动物,那鸟即有自身的特性,又有动物的特征
- 全部继承,完全封装基类,那么使用私有继承
- 全部继承,有选择封装基类,那么使用受保护继承
- 全部继承,不封装基类,那么用公有继承
- 继承和组合
- 继承:纵向;组合:横向
- 一个人换头发的颜色,继承
- 一个人由头发、眉毛等组成,组合
- 继承:纵向;组合:横向
- 继承方式提供给程序员对类进行封装的机制
-
多态性和虚函数
-
多态性和静态多态(重载)
-
面向对象编程的多态性
- 不同的对象调用同一个函数
- 调用的函数名相同,但是实现的细节不同
-
函数重载:静态多态或编译时多态
- 静态多态运行速度快,但是不灵活
-
动态多态——函数重写
-
重写,必须将父类的成员函数设定为虚函数Virtual
-
在定义声明时函数参数写成基类,然后再实际运行时将函数参数写成子类时,可以直接调用子类的成员变量。如果想要调用子类的函数时,需要在基类相同的函数前添加virtual关键词声明子类重写,因为此时默认调用基类的函数。(子类型自动转换成基类型)
-
当B是A的子类型时,意味着所有对A对象的操作,都可以对B对象进行。B重用A的操作来实现自己的操作。
- 向上转型是安全的
- 向上转型是自动完成的
- 向上转型的过程中会丢失子类型信息
- Warrior W; W.XiaoQuanQuan();
- Warrior war; //子类型
- Hero& hero = war; //父类型引用指向了子对象
- hero.XiaoQuanQuan(); //错误,丢失了子类型的信息
- 如果还想调用子类型方法,那么再进行向下转型
Warrior& war1 = (Warrior&)hero不安全,不推荐使用 - 为了更好体现子类重写父类方法,在子类重写的方法后写入关键词override
class Hero { public: virtual void Move(); //此时编译器就明白,派生类重写了基类的Move函数 //也就是说子类重写了,调用时,调用子类重写的函数 } class Warrior : public Hero { void Move() override; } class core { void MoveRole(Hero& hero) //定义基类类型 { hero.Move() } } Warrior war1; core.MoveRole(war1); //虽然定义中函数参数的类型时基类,但是这个地方填入的参 数类型是子类。由于子类中使用virtual重写了hero中的 Move(),所以会自动调用子类中的Move(). //如果没有重写函数的话,就会默认调用基类的Move()函数.
-
-
-
在升级程序时,一般使用继承的方法,千万不要修改基类
虚函数的工作原理
-
虚函数是有指针的,如果子类修改虚函数的话,则子类的虚函数指针会发生变化
-
其他知识点
- 构造函数不能是虚函数
- 析构函数应该是虚函数,直接加上吧
- 除非类不做基类
- 通常应该为基类提供一个虚析构函数,即使它不需要析构函数
- 如果不加virtual就只会调用父类析构,加上virtual会先调用派生类析构再调用父类析构
- 友元不能是虚函数
- 抽象类天生就是父类
-
纯虚函数和抽象类
-
将基类的虚函数写成:
virtual 返回类型 函数名(参数列表) const = 0形式时,称为纯虚函数,并且该函数没有函数体。- 这种等于0,并不是代表返回值为0,而是告诉编译器该函数是一个纯虚函数
- 纯虚函数要求必须有派生类来实现纯虚函数体的功能。
- 基类中不能定义函数体(可以提供默认实现,但还是需要在子类中定义),但是必须在子类中定义,否则报错。
-
如果类中所有方法均为纯虚函数,则该类就成为了抽象类,只能继承无法实例化
-
如果基类中有三个纯虚函数,而子类只实现了一个纯虚函数,那么此时子类仍然被看做是抽象类,无法进行实例化
-
总结

-
本文详细介绍了C++的基础知识,包括面向对象、类、虚函数的工作原理,深入探讨了指针、引用、内存管理、函数、函数指针、运算符重载、模板和多态性。特别强调了构造函数、析构函数的使用,以及纯虚函数和抽象类在继承中的角色。
493

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



