第一部分 基础语法入门
一、基础
1、变量与常量
1、变量
变量存在的意义:方便管理内存空间
2、常量
用于记录程序中不可更改的数据
#define 常量名 常量值
const 数据类型 常量名=常量值 ;
2、数据类型
1、整型
short 2字节
int 4字节
long Win4字节,32位linux 4字节,64位linux 8字节
long long 8字节
2、实型(浮点型)
float 4字节 7位有效数字
double 8字节 15-16位有效数组
默认情况下,输出一位小数,会显示6位有效数字
科学计数法
float f1=3e2; //3*10^2
float f2=3e-2; //3*0.1^2
3、字符型
char 1字节
字符型并不是把字符本身放在内存中存储,而是将对应的ASCII编码存储
4、字符串
char 变量名[]="abc"
string 变量名="abc" //include <string>
5、bool
1字节
6、数组
int a[5];
int b[5]={1,2,3,4,5};
int c[]={1,2,3};
3、sizeof关键字
统计数据类型所占内存大小
4、案例
#include <stdio.h> #include <stdlib.h> #include <string.h> void qOne() {int a = 4;a += (a++);//9 5+=4 执行a++ 返回4,a递增为5a += (++a);//10 5+=5//(a++)+= a;/*a++是一个右值,即不可修改的临时值,+=需要一个左值(即可以被赋值或修改的对象)*/(++a) += (a++);//10/*++a a变为5,返回5,++a作为左值a++ 当前a为5,返回5,a自增为65+=5*/printf("%d\n", a); } void Foo(char str[100]) { // 将str被视为char*传递,所以打印的是指针大小printf("%d\n", sizeof(str)); // 8 32位操作系统中,指针大小4字节,64位8字节 } void qTwo() {char str[] = "Hello word";char *p = str;int n = 10;printf("%d\n", sizeof(str)); // 11printf("%d\n", sizeof(p)); // 64位8printf("%d\n", sizeof(n)); // 4// Foo(str);void *p1 = malloc(100);// sizeof(p1) 返回的是指针本身的大小,而不是 malloc 分配的内存的大小。指针的大小依赖于编译器和操作系统的位数。printf("%d\n", sizeof(p1)); // 8 } struct Test {int Num;bool b;char sz[10]; };void qThree() {// 使用了c++的特性new,不能使用gcc编译// Test* p=new Test[10];struct Test *p = (struct Test *)malloc(10 * sizeof(struct Test));Test *p1 = &p[0];Test *p2 = &p[8];int n = p2 - p1;printf("%d", n); // 8 }void qThreePlus() {// 使用 malloc 分配内存,分配空间足够存放10个 Test 结构体void *p = new Test[10];/*void *p1 = (char *)p; // 指向第一个 Test 结构体void *p2 = (char *)p + 8 * sizeof(struct Test); // 指向第九个 Test 结构体printf("sizeof(struct Test):%d\n", sizeof(struct Test));// 因为 p1 和 p2 是 void*,我们需要将它们转换为适当的类型才能进行运算int n = (char *)p2 - (char *)p1;//128 *//* Test *p1 = (Test *)((char *)p + 0 * sizeof(struct Test)); // 指向第一个 Test 结构体Test *p2 = (Test *)((char *)p + 8 * sizeof(struct Test)); // 指向第九个 Test 结构体int n = p2 - p1;*/Test *p1 = (Test *)p; // 指向第一个 Test 结构体Test *p2 = (Test *)p + 8; // 指向第九个 Test 结构体int n = p2 - p1;printf("%d\n", n); // 输出 8 }char *qFour() {char *a = new char[128];char *p = a;strcpy(p, "aaaa");return p; } char *qFive() {// 拷贝不了:局部变量,栈上分配内存,函数结束,内存空间会被回收// char a[128];// 使用malloc或静态数组或者new,生命周期将被延长static char a[128];// 为什么不能是&a,他是指向包含128字符的数组的指针,不是字符,类型是char (*)[128],不是char*char *p = a;strcpy(p, "aaaa");return p; } char *strcpy(char *strDest, const char *strSrc) {char *oStrDest = strDest;while (*strSrc != '\0'){*strDest = *strSrc;strSrc++;strDest++;}*strDest = '\0';return oStrDest; } char *strcpyPlus(char *strDest, const char *strSrc) {int srcLen = strlen(strSrc);char *oStrDest = strDest;if (strDest < strSrc || strDest - strSrc >= srcLen){// 没有内存重叠问题while (*strSrc != '\0'){*strDest = *strSrc;strSrc++;strDest++;}*strDest = '\0';return oStrDest;}// 有内存重叠问题strDest = strDest + srcLen;strSrc = strSrc + srcLen - 1;*strDest = '\0';for (int i = 0; i < srcLen; i++){strDest--;*strDest = *strSrc;strSrc--;}return oStrDest; }void *memcpy(void *dest, const void *src, int size) {char *cDest = (char *)dest;const char *cSrc = (const char *)src;for (int i = 0; i < size; i++){cDest[i] = cSrc[i];}return dest; } void *memcpyOtherWay(void *dest, const void *src, int size) {char *cDest = (char *)dest;const char *cSrc = (const char *)src;for (int i = 0; i < size; i++){*cDest = *cSrc;cDest++;cSrc++;}return dest; }
二、函数
1、函数的分文件编写
作用:让代码结构更加清晰
1、创建.h头文件
2、创建.cpp源文件
3、在头文件写函数的声明
4、在源文件写函数的定义
//值交换 //1、swap.h #include <iostream> using namespace std;void swap(int a,int b);//2、swap.cpp #include "swap.h"void swap(int a,int b){int temp=a;a=b;b=temp;cout << "a=" << a <<endl;cout << "b=" << b <<endl; }//3、main.cpp #include <iostream> #inculde "swap.h" using namespace std;int main(){swap(1,2);.return 0; }
2、函数默认参数
函数的形参列表是可以有默认值的
#include <iostream>using namespace std; int add(int a,int b,int c); int add(int a,int b=20,int c=30){return a+b+c; } int main(){add(10);//60add(10,30);//70} /* 1、如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认值int add(int a,int b=20,int c=30) 2、如果函数声明有默认参数,函数的实现就不能有默认参数(声明和实现只能有一个有默认参数)*/
3、函数占位参数
#include<iostream> using namespace std; void func1(int a,int){} //占位参数可以有默认参数 void func2(int a,int =20){} int main(){func1(10,20);}
4、函数重载
条件:
1、同一个作用域
2、函数名称相同
3、函数参数类型不同或者个数不同或者顺序不同
4、返回值不能作为重载条件
#include<iostream>using namespace std;//作用让函数名相同,提高复用性 void func1(){} void func1(int a){} //引用作为重载条件 void func2(int &a){} void func2(const int &a){}//函数重载碰到默认参数 语法通过调用传入1参数会产生二义性 void func3(int a){} void func3(int a,int b=10){} int main(){int a=10;func2(a);//第一个 因为a是可读可写的func2(10);//第二个 有const合法}
三、指针
1、指针的定义和使用
1、作用:可以通过指针间接访问内存
(1)内存编号是从0开始记录的,一般用16进制表示
(2)可以通过指针变量保存地址
启动一个程序,系统会给其分配内存空间,内存空间最小1字节,内存中每一个字节都有编号,这个编号称为内存的地址
2、定义与使用
#include <iostream> using namespace std;int main(){//定义指针int a=10;int* p;p=&a;//指针就是地址cout << "a的地址" << &a <<endl;cout << "指针p为" << p << endl; //0xABCD//使用指针//可以通过解引用(*)的方式来找到指针指向的内存*p=100;cout << "a=" << a <<endl;//100return 0; }
2、 指针所占的内存空间
32位操作系统指针占用4字节,64位8个字节
3、空指针与野指针
1、空指针
指针变量指向内存中编号为0的空间
用途:用于初始化指针变量
注意:空指针指向的内存空间不可以访问
内存编号0~255为系统占用内存,不允许用户访问
#include <iostream> using namespace std;int main(){//空指针:用于给指针变量初始化,不初始化就不知道指向哪里int *p=NULL;//语法通过,但是不可以访问,0~255之间内存编号不允许访问*p=100;return 0; }
2、野指针
指针变量指向非法的内存空间,不是我们申请的空间
#include <iostream> using namespace std;int main(){//指针指向内存地址编号为0x1100的空间int* p=(int*)0x1100;//访问野指针出错cout<< *p <<endl;return 0;}
3、万能指针
不可以定义void类型的变量,因为编译器不知道给变量分配多大的空间,但是可以定义void *类型,因为指针都是4个字节
int main() {int a = 10;void *p = (void *)&a;// printf("%d\n", *p);//err p是void*,不知道取几个字节的大小printf("%d\n", *(int *)p2); // 10}
4、const修饰指针
const修饰指针:常量指针
int a=10;
const int* p=&a;(const修饰的是int* 所以值不能改)
指针的指向可以改,但该指针指向的值不可以改
const修饰常量:指针常量
int a=10;
int* const p=&a;(const修饰的是p 所以地址不能改)
const后面跟的p是变量,修饰后成为常量
指针的指向不能改,但指向的值可以改
const即修饰指针又修饰常量
const int* const p=&a;
5、指针和数组
int a[5];
数组名a代表数组,也代表第0个元素地址
在数值上:a==&a[0]==&a==地址
若&a[0]=0x0001,则&a[0]+1=0x0005(元素地址+1=跨过一个元素),等同于a+1
&a+1跨过整个数组==0x0021
void array() {int a[5];/*数组名a代表数组,也代表第0个元素地址a==&a[0]==&a==地址若&a[0]==01,则&a[0]+1=05(元素地址+1跨过一个元素),等同于a+1&a+1跨过整个数组=21*/printf("%d\n", a);printf("%d\n", &a);printf("%d\n", &a[0]);printf("%d\n", &a[0] + 1);printf("%d\n", &a[1]);printf("%d\n", a + 1);printf("%d\n", &a + 1);printf("%d\n", &a[4] + 1); }
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
a[0][0]第0行第0个元素
&a[0][0]第0行第0个元素地址 +1跨过一个元素
a[0]第0行一维数组名 +1跨过一个元素
&a[0]第0行地址 +1跨过一行
a二维数组名,首行地址&a[0] +1跨过一行
&a二维数组地址 +1跨过整个数组
//元素个数
int num=sizeof(a)/sizeof(a[0][0]);
//行数 总大小/一行的大小
int row=sizeof(a)/sizeof(a[0]);
//列数 行大小/一个元素大小
int column=sizeof(a[0])/sizeof(a[0][0]);
6、指针和函数
void swap(int *p1,int *p2){int temp=*p1;*p1=*p2;*p2=temp; } int main(){int a=10;int b=20;swap(&a,&b); }
void bubbleSort(int * arr,int len){//int *arr可以写成int arr[]for(int i=0;i<len-1;i++){for(int j=0;j<len-i-1;j++){if(arr[j]>arr[j+1]){int temp=arr[j];arr[j]=arr[j+1];arr[j+1]=temp;}}} }
#include <stdio.h>int add(int a, int b) {return a + b; } int subtract(int a, int b) {return a - b; }int main() {int num = 10;int *pNum = #// 基本指针类型printf("%d\n", *pNum); // 10printf("%p\n", &pNum);printf("%p\n", pNum); // 保存的num的地址,和&num一样printf("%p", &num);// 函数指针int (*operation)(int, int);operation=add;printf("a+b=%d\n",operation(5,3));return 0; } /*** 指针==地址==编号* 指针变量:存放指针(地址)的变量* 指针:* 基本指针类型* int* char* float* double* void*可以指向任何类型数据,使用前需类型转换* 指向指针的指针* int** 指向int*的指针,即指针的指针,用于动态数组等场景* 函数指针:指向这个函数* 返回值类型 (*函数指针名)(参数类型)* 在使用时,对一个表达式取*,就会对表达式减一级*,如果对表达式取&,就会加一级** 32位操作系统中,指针大小4字节,64位8字节****/
四、结构体
1、定义
struct 类型名称{
成员列表
};
struct Student {int age;string name; }s3; int main(){ //1、struct Student s1其中struct可以省略struct Student s1;s1.name="张三";s1.age=18; //2、struct Student s2={...}struct Student s2={18,"张三"} //3、定义时顺便赋值s3s3.age=20;s3.name="王五"; }
2、结构体数组
struct 数组名[个数]={{},{},{}...}
struct Student{string name;int age; };int main(){struct Student stuArr[2]={{"张三",18},{"王五",20}};stuArr[1].name="张武"; }
3、结构体指针
->
struct Student{string name;int age; }; int main(){Student s={"张三",18};Student* p=&s;p->name="王五"; }
4、结构体嵌套结构体
struct student{string name;int age; }; struct teacher{string name;int age;struct student stu; }; int main(){teacher t;t.stu.name="小小";}
5、结构体做函数参数
struct Student {int age;string name; }; void printStu1(struct Student s){} void printStu2(struct Student *p){p->name="李四";p->age=32; }int main(){struct Student s;s.name="张三";s.age=18; //值传递printStu1(s); //址传递printStu2(&s) }
如果不想修改主函数中的数据,用值传递,反之用地址传递
6、结构体中const使用
作用:用const防止误操作
struct student{string name;int age; }; void printStu(const student * s){} int main(){student s={"张三",20};printStu(&s); }
值传递会复制副本,所以速度慢
7、案例
struct Student{string name;int age; }; struct Teacher{string name;struct Student arr[5]; };void allocateSpace(struct Teacher arr[],int len){string nameSeed="ABCDE";for(int i=0;i<len;i++){arr[i].name="Teacher_";arr[i].name+=nameSeed[i];for(int j=0;j<5;j++){arr[i][j].name="Student_";arr[i][j].name+=nameSeed[j];arr[i][j].age=18;}} } int main(){Teacher tArr[3];int len=sizeof(tArr)/sizeof(tArr[0]);allocateSpace(tArr,len); }
第二部分 核心编程
一、内存模型
C++程序执行时,将内存大致分为4个区域
代码区:存放函数体的二进制代码,由操作系统进行管理
全局区:存放全局变量和静态变量以及常量
栈区:由编译器进行自动分配与释放,存放函数的参数值,局部变量
堆区:由程序员分配和释放,若不释放,程序结束由操作系统回收
1、程序运行前
在程序编译后,生成exe可执行程序,未执行程序前分为两个区域
代码区:
存放CPU执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量(static)
包含了常量区:字符串常量和其他常量(const修饰的全局变量)也存放在此
该区域数据在程序结束后由操作系统释放
注意:局部变量和局部常量不在全局区
2、 程序运行后
分为栈区和堆区
栈区
由编译器自动分配释放,存放函数参数值,局部变量
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区
new
delete
释放数组delete[] arr;
二、引用
1、引用的基本使用
作用:给变量起别名
语法:数据类型 &别名=原名
2、引用的注意事项
引用必须初始化
初始化后不可改变
int main(){int a=10;int &b=a; }
3、引用做函数参数
void swap(int &a,int &b){ //形参中的a是main中a的别名int temp=a;b=a;a=temp; } int main(){int a=20;int b=20;swap(a,b); }
4、引用做函数返回值
不要返回局部变量的引用
函数的调用可以作为左值
int& func(){static int a=10;return a; } int main(){int &ref=func();//相当于int* const ref=&aref=20;//内部发现ref为引用,自动转为*ref=20test()=1000; }
5、引用的本质
引用的本质是一个指针常量(指针指向不可变)
6、常量引用
作用:常量引用主要用来修饰形参,防止形参改变实参
void showValue(const int& v){//v+=10; } int main(){int a=10;showVlue(a);//int& ref=10;引用本身需要一个合法的内存空间,因此错误const int& ref=10;//加上const之后,编译器将代码进行修改 int temp=10;const int& ref=temp;//ref=20;加入const之后成为只读,不可修改 }
三、类和对象
1、封装
#include<iostream> #include<string>using namespace std;class Student{ public://成员属性/成员变量string m_Name;int m_Id;//成员函数/成员方法void show(){}void set(string name,int id){this->m_Name=name;this->m_Id=id;} }; int main(){Student s1;s1.m_Name="张三";s1.m_Id=1;s1.show(); }
权限
public 公共权限 成员类内可以访问,类外可以访问
protected 保护权限 成员类内可以访问,类外不可以访问 继承子可以访问父保护内容
private 私有权限 成员类内可以访问,类外不能访问
struct和class的区别为:默认访问权限不同
struct:public
class:private
2、对象的初始化和清理
2.1 构造与析构
构造函数
有参构造/无参构造
普通构造/拷贝构造
析构函数:不可以有参数,不能重载
#include<iostream>using namespace std;class Person{ public:Person(){//没有返回值 函数名和类名相同 可以有参数,可以重载 创建对象时,会自动调用一次 默认提供}Person(string name,int age){this->name=name;thia->age=age;}//拷贝构造Person(const Person &p){this->age=p.age;this->name=p.name;}~Person(){//没有返回值 函数名和类名相同 不可以有参数 对象销毁前,会自动调用一次 默认提供}private:string name;int age; }; int main(){//构造函数调用方式1Person p1;//栈上的数据,main执行完毕会释放Person p2("li",10);Person p3(p2);//不会创建对象,编译器会认为是函数的声明Person p4();//构造函数调用方式2Person p5=Person("zhang",30);//匿名对象 当程序执行结束后,系统立刻回收(这行代码执行后回收)Person("zhang",30);//不能利用拷贝构造函数初始化匿名对象,编译器会任务Person(p3) === Person p3; 对象声明重定义构造函数调用方式3 隐式调用-拷贝构造Person p6=p3; }
2.2 拷贝构造调用时机
拷贝构造函数调用时机
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
#include<iostream>using namespace std;class Pereson{ public:Person (){}Person(int age){this->age=age;}Person(const Person &p){this->age=p.age;}~Person(){}private:int age; }; void doWork1(Person p){ //值传递的本质会拷贝出一个临时副本出来 } Person doWork2(){Person p1;//会根据p1拷贝出一个新的对象返回return p1; } //拷贝构造函数调用时机 void demo1(){//1、使用一个已经创建完毕的对象来初始化一个新对象Person p1(10);Person p2(p1);//2、值传递的方式给函数参数传值Person p3;doWork1(p3);//3、值传递返回局部对象Person p4=doWork2(); }
2.3 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
1、默认构造
2、默认析构
3、默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则
如果用户定义有参构造,c++不再提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
2.4 深拷贝与浅拷贝
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
总结:如果属性有在堆区开辟内存的,一定要提供拷贝构造函数,防止浅拷贝带来的问题
#include<iostream>using namespace std;class Person{ public:Person(){}Person(int age,int height){this->age=age;//堆区创建的返回就是int*this->height=new int(height);}~Person(){if(height!=NULL){delete height;height=NULL;}} private:int age;int *height; };void demo1(){Person p1(18,160);//报错 Person p2(p1); /* 原因:创建了p1,p2两个对象 如果利用编译器提供的拷贝构造函数,会做浅拷贝操作 堆区开辟的空间,浅拷贝将地址拷贝 执行析构时p2先被释放,同时释放堆区内存,p1在释放就会出错 浅拷贝的问题就是堆区内存重复释放,使用深拷贝解决*/}
#include<iostream>using namespace std;class Person{ public:Person(){}Person(int age,int height){this->age=age;//堆区创建的返回就是int*this->height=new int(height);}Person(const Person &p){//深拷贝this->age=age;this->height=new int(*p.height); }~Person(){if(height!=NULL){delete height;height=NULL;}} private:int age;int *height; };void demo1(){Person p1(18,160);Person p2(p1);}
2.5 初始化列表
初始化列表
#include <iostream>using namespace std;class Person{ public://初始化列表初始化Person():age(10),height(20){}Person(int a,int h):age(a),height(h){}private:int age;int height; };
2.6 类对象作为类成员
类对象作为类成员
#include<iostream>using namespace std;class A{};class B{ public:A a; };void demo1(){B b; } //先有A再有B
当其他类对象作为本类成员,构造时候先构建类对象,在构造自身,先析构本身,在析构类对象
2.7 静态成员
静态成员
静态成员就是在成员变量和成员函数前加上关键字static
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
#include<iostream>using namespace std;class A{ public:static int m_A;static void func(){} };int A::m_A=100; //静态成员变量,不属于某个对象,所有对象共享同一份数据 //因此,两种访问方式:通过对象访问/通过类名访问 void demo1(){A a1;//a1.m_A=200;A a2;a2.m_A=200;//A::m_A=300; }
3、c++对象模型和this
3.1 成员变量和成员函数
成员变量和成员函数分开存储
#include<iostream>using namespace std;/* 变量只能存储在 min(他的长度,pack参数)的整数倍地址上char 地址为1的倍数short 2的倍数 0 2 4 8int 4的倍数 0 4 8 12double 8的倍数 0 8 16 24 结构体整体对齐跟他的 min(最长的字段,pack)整数倍对齐 数组按照数组类型来对齐 结构体嵌套结构体按照被嵌套的最大元素长度对齐 */struct AA{long long a; //8char b; //int c; //和12对齐char d[2]; //偏移量目前16+2+(6)=8*3} struct BB{long long a;//8char b;//1+7struct AA c;//按8*2对齐char d[2];//偏移量16+24+2+(6)=8*6 }/*#pragma pack(show) 默认16 //16一般超过结构体中最大大小,所以没影响 如果 #pragma pack(2) 那么int地址 0 2 4 6struct CC{long long a; //8char b; //2int c; //和10对齐char d[2]; //偏移量目前14+2=2*8}*/
#include<iostream>class A{}; class Person{ public:int a;//非静态成员变量 属于类的对象 4字节static int b; //静态成员变量 不属于类对象void func1(){}//非静态成员函数 不属于类的对象static void func2(){}//静态成员函数 不属于类的对象}; int Person::b;void demo1(){ //空对象占用内存空间sizeof(a) 为1字节A a; //C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占用内存的位置 每个空对象也应该有一个独一无二的内存地址} void demo2(){Person p;//sizeof(p); 4字节 只有非静态成员变量属于类的对象}
3.2 this指针
this指针
成员变量和成员函数分开存储
每个非静态成员函数只会诞生一份函数实例,也就是多个同类型的对象会公用一块代码
this指针指向被调用的成员函数所属的对象
this指针的用途
当形参和成员变量同名时,使用this区分
在类的非静态成员函数中返回对象本身,可使用return *this,this指针指向对象,解引用返回对象本身
#include<iostream>using namespace std;class Person{ public://如果返回值写Person 会调用拷贝构造函数返回新的对象 Person& PersonAddAge(Person &p){this->age+=p.age;return *this;}int age;}; void demo1(){Person p1;p1.age=10;Person p2;p2.age=10;p2.PersonAddAge(p1).PersonAddAge(p2);//30 }
空指针可以访问成员函数,但要注意this,空指针不能访问属性
3.3 常函数与常对象
const修饰成员函数
成员函数加const成为常函数
常函数不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象
声明对象前加const称为常对象
常对象只能调用常函数
#include <iostream>using namespace std;class Person{ public://this指针的本质 指针常量 指针的指向不可以改变//加const 本质修饰的this指针,让指针指向的值不可以改变void show() const{//this->a=100;this->b=10;}int a;mutable int b;//在常函数中也想修改 };void demo2(){const Person p;//常对象,属性不可以修改,只能调用常函数,不能调用普通函数,因为普通函数可以修改属性//p.a=100;p.b=100; }
4、友元
友元:让一个函数或者类,访问另一个类中私有成员
全局函数做友元
#include<iostream>using namespace std;class Building{ //goodGuy可以访问私有成员了 friend void goodGuy(Building *building); public:Building(){this->sittingRoom="客厅";this->bedRoom="卧室;"} public:string sittingRoom; private;string bedRoom;}; //全局函数 void goodGuy(Building *building){println(building->sittingRoom);println(building->bedRoom); } void demo1(){Building building;goodGuy(&building);}
类做友元
#include<iostream>using namespace std;//声明 class Building;class Building{ //GoodGuye类可以访问私有成员了 friend class GoodGuy; public:Building(); public:string sittingRoom; private;string bedRoom;}; //类外可以初始化成员函数 Building::Building(){this->sittingRoom="客厅";this->bedRoom="卧室;" }class GoodGuy{ public:GoodGuy(){building=new Building;}void visit(); Building *building; };void GoodGuy::visit(){println(building->sittingRoom);println(building->bedRoom); }void demo1(){GoodGuy goodGuy;goodGuy.visit();}
成员函数做友元
#include<iostream>using namespace std;class Building{ //visit方法可以访问私有成员了 friend void GoodGuy::visit(); public:Building(); public:string sittingRoom; private;string bedRoom;}; //类外可以初始化成员函数 Building::Building(){this->sittingRoom="客厅";this->bedRoom="卧室;" }class GoodGuy{ public:GoodGuy();void visit(); Building *building; };GoodGuy::GoodGuy(){buildint=new Building; } void GoodGuy::visit(){println(building->sittingRoom);println(building->bedRoom); }void demo1(){GoodGuy goodGuy;goodGuy.visit();}
5、运算符重载
运算符重载:对已有运算符重新进行定义,赋予另一种功能,以适应不同数据类型
加号运算符重载
#include<iostream>using namespace std;//1、成员函数重载+ class Person{ public:int a;int b;Person operator+(const Person &p){Person temp;temp.a=this->a+p.a;temp.b=this->b+p.b;return temp;} }; //2、全局函数重载+ Person operator+(const Person &p1,const Person &p2){Person temp;temp.a=p1.a+p2.a;temp.b=p1.b+p2.b;return temp; }void demo1(){Person p1;p1.a=10;p1.b=20;Perosn p2;p2.a=30;p2.b=40;Person p3=p1+p2;//本质调用 Person p3=p1.operator+(p2)//Person p3=operator+(p1,p2)//p3.a==40;p3.b==60}
#include <iostream>using namespace std;class Person{ friend ostream& opetator<<(ostream &cout,Perosn &p); private:int a ; int b; }; ostream& opetator<<(ostream &cout,Perosn &p){cout<<"a="<<p.a<<" b="<<p.b;return cout; }
class MyInteger {friend ostream& operator<<(ostream& out, MyInteger myint);public:MyInteger() {m_Num = 0;}//前置++MyInteger& operator++() {//先++m_Num++;//再返回return *this;}//后置++MyInteger operator++(int) {//先返回MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;m_Num++;return temp;}private:int m_Num; };ostream& operator<<(ostream& out, MyInteger myint) {out << myint.m_Num;return out; }//前置++ 先++ 再返回 void test01() {MyInteger myInt;cout << ++myInt << endl;cout << myInt << endl; }//后置++ 先返回 再++ void test02() {MyInteger myInt;cout << myInt++ << endl;cout << myInt << endl; }
class Person { public:Person(int age){//将年龄数据开辟到堆区m_Age = new int(age);}//重载赋值运算符 Person& operator=(Person &p){if (m_Age != NULL){delete m_Age;m_Age = NULL;}//编译器提供的代码是浅拷贝//m_Age = p.m_Age;//提供深拷贝 解决浅拷贝的问题m_Age = new int(*p.m_Age);//返回自身return *this;}~Person(){if (m_Age != NULL){delete m_Age;m_Age = NULL;}}//年龄的指针int *m_Age;};
class Person { public:Person(string name, int age){this->m_Name = name;this->m_Age = age;};bool operator==(Person & p){if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){return true;}else{return false;}}bool operator!=(Person & p){if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){return false;}else{return true;}}string m_Name;int m_Age; };void test01() {//int a = 0;//int b = 0;Person a("孙悟空", 18);Person b("孙悟空", 18);if (a == b){cout << "a和b相等" << endl;}else{cout << "a和b不相等" << endl;}if (a != b){cout << "a和b不相等" << endl;}else{cout << "a和b相等" << endl;} }
class MyPrint { public:void operator()(string text){cout << text << endl;}}; void test01() {//重载的()操作符 也称为仿函数MyPrint myFunc;myFunc("hello world"); }class MyAdd { public:int operator()(int v1, int v2){return v1 + v2;} };void test02() {MyAdd add;int ret = add(10, 10);cout << "ret = " << ret << endl;//匿名对象调用 cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl; }
6、继承
6.1 基本语法
class Base{};class A:public Base{}
class 子类(派生类): 继承方式 父类(基类)
6.2 继承方式
公共继承
保护基础
私有继承
缩小了权限
6.3 继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去
6.4 继承中构造和析构顺序
父类构造--子类构造--子类析构--父类析构
6.5 继承同名成员处理方式
当子类与父类出现同名的成员的成员,如何通过子类对象,访问子类或父类同名数据
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
6.6 继承同名静态成员处理方式
静态成员和非静态成员出现同名
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
class Base { public:static void func(){cout << "Base - static void func()" << endl;}static void func(int a){cout << "Base - static void func(int a)" << endl;}static int m_A; };int Base::m_A = 100;class Son : public Base { public:static void func(){cout << "Son - static void func()" << endl;}static int m_A; };int Son::m_A = 200;//同名成员属性 void test01() {//通过对象访问cout << "通过对象访问: " << endl;Son s;cout << "Son 下 m_A = " << s.m_A << endl;cout << "Base 下 m_A = " << s.Base::m_A << endl;//通过类名访问cout << "通过类名访问: " << endl;cout << "Son 下 m_A = " << Son::m_A << endl;cout << "Base 下 m_A = " << Son::Base::m_A << endl; }//同名成员函数 void test02() {//通过对象访问cout << "通过对象访问: " << endl;Son s;s.func();s.Base::func();cout << "通过类名访问: " << endl;Son::func();Son::Base::func();//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问Son::Base::func(100); }
6.7 多继承
class 子类:继承方式 父类1,继承方式 父类2
6.8 菱形继承
菱形继承:
两个派生类继承同一个基类
某个类同时继承两个派生类
cl /d1 reportSingleClassLayout类 file.cpp
class Animal { public:int m_Age; };//继承前加virtual关键字后,变为虚继承 //此时公共的父类Animal称为虚基类 class Sheep : virtual public Animal {}; class Tuo : virtual public Animal {}; class SheepTuo : public Sheep, public Tuo {};void test01() {SheepTuo st;st.Sheep::m_Age = 100;st.Tuo::m_Age = 200;cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;cout << "st.m_Age = " << st.m_Age << endl; }
- 虚继承可以解决菱形继承问题
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义利用
7、多态
7.1 基本概念
#include<iostream> using namespace std;class Animal{ public: //如果想执行让猫说话,那函数地址就不能提前绑定需要在运行期间绑定--地址晚绑定void speak(){//virtual void speak(){println("dongwu");}}; class Cat :public Animal{ public:void speak(){println("miao");} }; class Dog:public Animal{ public:void speak(){println("wang");} }; void doSpeak(Animal &animal){animal.speak(); }void demo1(){Cat cat;doSpeak(cat);//执行的时动物说话,因为地址早绑定,在编译阶段就确定了函数地址}
动态多态满足条件
有继承关系
子类重写父类虚函数
父类的指针/引用 指向子类的对象
7.2 多态剖析
写了一个虚函数virtual void speak(),类的内部发生了改变,多了一个虚函数指针(vfptr),指向虚函数表(vftable),表中记录着 虚函数的入口地址,当子类继承了父类,同样会继承父类结构,当子类重写父类虚函数,会将自身虚函数表中虚函数入口地址替换为子类虚函数地址。所以当父类指针或引用指向子类对象时,发生多态
7.3 纯虚函数和抽象类
当类中有了纯虚函数,这个类称为抽象类
无法实例化对象
子类必须重写抽象类中纯虚函数,否则也属于抽象类
class Base { public://纯虚函数//类中只要有一个纯虚函数就称为抽象类//抽象类无法实例化对象//子类必须重写父类中的纯虚函数,否则也属于抽象类virtual void func() = 0; };class Son :public Base { public:virtual void func() {cout << "func调用" << endl;}; };void test01() {Base * base = NULL;//base = new Base; // 错误,抽象类无法实例化对象base = new Son;base->func();delete base;//记得销毁 }
7.4 饮品
#include <iostream>using namespace std;class AbstractDrink{ public://煮水virtual void Boil()=0;//冲泡virtual void Brew()=0;//倒杯virtual void PourInCup()=0;//加入佐料virtual void PutSomething()=0;//制作饮品void makeDrink(){Boil();Brew();PourInCup();PutSomething();} }; Class Coffee :public AbstractDrink{void Boil(){}void Brew(){}void PourInCup(){}void PutSomething(){}}; void doWork(AbstractDrink *drink){drink->makeDrink();delete drink; } void demo1(){doWork(new Coffee);}
7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类析构
解决:将父类中析构函数改为虚析构或纯虚析构
#include<iostream> using namespace std;class Animal{ public:virtual void speak()=0;virtual ~Animal(){} }; class Cat :public Animal{ public:Cat(string name){this->name=new string(name);}void speak(){}string *name;~Cat(){if (this->name!=NULL){delete this->name;this->name=NULL;}} }; void demo1(){Animal *animal=new Cat("加菲");animal->speak();//父类指针在析构的时候,不会调用子类中析构,导致子类中如果有堆区数据,造成内存泄漏delete animal; }
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类
7.6 电脑组装
#include<iostream>using namespace std;class CPU{ public:virtual void calculate()=0; }; class VideoCard{ public:virtual void display()=0; }; class Memory{ public:virtual void storage()=0; }; class IntelCpu:public CPU{void calculate(){} }; class IntelVideoCard:public VideoCard{void display(){} }; class IntelMemory:public Memory{void storage(){} }; class Computer{ public:Computer(CPU *cpu,VideoCard *vc,Memory *mem){this->cpu=cpu;this->vc=vc;this->mem=mem;}void doWork(){cpu->calculate();vc->display();mem->storage();}~Computer(){if(this->cpu!=NULL){delete this->CPU;this->cpu=NULL;}if(this->vc!=NULL){delete this->vc;this->vc=NULL;}if(this->mem!=NULL){delete this->mem;this->mem=NULL;}} private:CPU *cpu;VideoCard *vc;Memory *mem; }; void demo1(){Computer * com=new Computer(new InterCPU,new InterVideoCard,new InterMemory);com->doWork();delete com; }
8、文件操作
8.1 文本文件
头文件<fstream>
文本文件:文本以文本的ASCII码的形式存储
二进制文件:以二进制的形式存储
三大类:
ofstream:写操作
ifstream:读操作
fstream:读写
文本文件写文件
1、包含头文件
#include<fstream>
2、创建流对象
ofstream ofs;
3、打开文件
ofs.open("路径",打开方式);
4、写数据
ofs<<"写入的数据";
5、关闭文件
ofs.close();
文件打开方式
打开方式 解释 ios::in 为读文件而打开文件 ios:out 写文件 ios:ate 初始位置:文件尾 ios:app 追加方式写文件 ios:trunc 如果文件存在先删除,在创建 ios::binary 二进制方式 注意:文件打开方式可以配合使用,利用|操作符
#include <fstream>void test01() {ofstream ofs;ofs.open("test.txt", ios::out);ofs << "姓名:张三" << endl;ofs << "性别:男" << endl;ofs << "年龄:18" << endl;ofs.close(); }
文本文件读文件
#include<iostream> using namespace std;#include<fstream>void demo1(){ifstream ifs;ifs.open("test.txt",ios::in);if(!ifs.is_open()){return;}//1、字符数组char buf[1024]={0}; while(ifs>>buf){cout<<buf<<endl;} //2、字符数组char buf[1024]={0}; while(ifs.getline(buf,sizeof(buf))){cout<<buf<<endl;}//3、stringstring buf;while(getline(ifs,buf)){cout<<buf<<endl;}//4、char cchar c;while((c=ifs.get())!=EOF){/EOF end of filecout<<c;}ifs.close();}
8.2 二进制文件
写文件
#include<iostream> using namespace std; #include <fstream>class Person{ public:char m_Name[64];int m_Age; };void demo1(){ofstream ofs;ofs.open("person.txt",ios::out|ios::binary);Person p={"张三",18};ofs.write((const char*)&p,sizeof(Person));ofs.close(); }
读文件
#include<iostream> using namespace std; #include <fstream>class Person{ public:char m_Name[64];int m_Age; };void demo1(){ifstream ifs;ofs.open("person.txt",ios::in|ios::binary);if(!ifs.is_open()){return; }Person p;ifs.read((char*)&p,sizeof(Person));//p.m_Name p.a_Ageofs.close(); }