导图社区 Cpp_learning
入门C 学习,C++是一种功能强大的编程语言,它既保留了C语言的低级特性和指针操作,又增加了面向对象编程的特性。
编辑于2024-01-01 15:30:23C++
类
类的定义
类型
public:公共变量和公共成员函数
private:
protect:
成员函数函数
特殊的公有成员函数
构造函数
主要作用于创建函数时对对象成员的属性赋值。
构造语法
类名(){} 1.构造函数,没有返回值也不写void 2.函数名称与类名相同 3.构造函数可以有参数,因此可以发生重载 4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
可以在后面加冒号,写父类的构造函数,用来初始化父类的成员
B1(int a=0,int b=0):A(b) //就是这句 { y1=a; }
析构函数
主要作用于在对象销毁前,执行一些清理工作(如释放new开辟在堆区的空间)。
~类名(){} 1.析构函数,没有返回值也不写void 2.函数名称与类名相同,在名称前加上符号 ~ 3.析构函数不可以有参数,因此不可以发生重载 4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
用来始化和清理对象
这两个函数将会被编译器自动调用。
两个函数的特点
必须定义在public里面,才可以调用
构造函数的名称和类名相同,一个类可以有多个构造函数,只能有一个析构函数。不同的构造函数之间通过参数个数和参数类型来区分;
静态函数
static float average(); //平均数 //静态成员函数的声明
静态成员函数可以直接引用私有的静态数据成员(不需要加类名或者对象名)
不能访问普通成员
类的创建
栈中分配内存,隐式调用构造函数
Test test1(1);
Test test1;
没有参数时没有括号
栈中分配内存,显式调用构造函数
Test test2 = Test(1);
Test test2 = Test;
堆内存中分内存,使用new关键字显式调用构造函数
Test* test3 = new Test(1);
new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。
有了对象指针后,可以通过箭头->来访问对象的成员变量和成员函数,这和通过结构体指针来访问它的成员类似
个人感觉相当于c语言里的malloc一个sizeof(Test)的堆空间
友元函数
定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
仅在类中声明,在类外定义,且不属于该类
在类定义中该函数原型前使用关键字 friend
友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
继承
已有的类称为基类,新建的类称为派生类。
继承类型
公有继承(public):
当一个类派生自 公有 基类时,基类的 公有 成员也是派生类的 公有 成员,基类的 保护 成员也是派生类的 保护 成员,基类的 私有 成员不能直接被派生类访问,但是可以通过调用基类的 公有 和 保护 成员来访问。
保护继承(protected):
当一个类派生自 保护 基类时,基类的 公有 和 保护 成员将成为派生类的 保护 成员。
私有继承(private):
当一个类派生自 私有 基类时,基类的 公有 和 保护 成员将成为派生类的 私有 成员。
多继承
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
class Rectangle: public Shape, public PaintCost
重载
重载决策
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。
也就是同一个名字,但是可以有不同的参数
重载函数
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
重载运算符
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。
const Box operator+(const Box &box)const { }
第一个const:返回值为常数
(a + b) = 5; // 在C++中不允许如此做,会报错
(p1 + p2) = Point(40, 50); // 如果不加第1个const,则这里不会报错
我们将 (p1+p2) 的结果看作常数,常数不允许被赋值。
第二锅const:可接受常量作为参数
给接收的参数加上 const,就可以同时接受 const参数 与 非const参数,扩大了接受参数的范围。
第三个const:可以被常量调用
const Point p1 = Point(10, 20); Point p2 = Point(20, 30); Point p3 = p1 + p2;
p1对象是个常量,Point p3 = p1 + p2; 实际上等价于 p1.operator+(p2);,实际上是拿常量p1调用了函数,因此这个函数必须可以被常量调用,这就是第3个const的作用。
why_int operator/(const why_int &One);
运算符类型
双目
const Point operator+(const Point &point) const { return Point(m_x + point.m_x, m_y + point.m_y); }
单目
+a -a
const Point operator-() const { return Point(-m_x, -m_y); }
+= -=
返回的是自己,a = a+1,因此直接 引用传参, 传进来的就是那个实参,返回的也是那个实参
int a = 1; (a += 1) = 2;
(a+=1)是允许被赋值的,我们可以理解为 (a+=1) 的返回值依旧是一个变量
Point& operator+=(const Point &point) { // 返回值要注意 this->m_x += point.m_x; this->m_y += point.m_y; return *this; }
多态
有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
就是用A类的指针p分别指向B类和C类的一个对象,ABC类都有相同的子函数getvalue(),那么p->getvalue的时候不会因为p的指针类型是A而调用A的getvalue()函数,而是根据p指向的那个对象的类型来判断使用哪个类的成员函数
实现形式
父类成员函数声明前放置关键字 virtual
virtual int area() { cout << "Parent class area :" <<endl; return 0; }
虚函数
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。 我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数
virtual int area() = 0;
告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
数据抽象
内涵
只向外界提供关键信息,并隐藏其后台的实现细节
您的程序可以调用 sort() 函数,而不需要知道函数中排序数据所用到的算法。
方法
就是把类的数据成员全部定义为私有,这样外部不能修改内部变量,而只能通过public中的接口函数来访问或修改。其中接口函数是你自己写的,知道其细节。外部只需要知道这个接口的作用,而不需要了解你的具体实现。
好处
类的内部受到保护,不会因无意的用户级错误导致对象状态受损
类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
设计策略
抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。
数据封装
数据和操作数据的函数捆绑在一起的机制
能避免受到外界的干扰和误用
C++ 程序中,任何带有公有和私有成员的类都可以作为数据封装和数据抽象的实例
C++ 接口
用抽象类来实现的
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
目的
是为了给其他类提供一个可以继承的适当的基类
用来继承的,不能直接实例化
STL
C++ STL(Standard Template Library 标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
六大组件
核心三大组件
容器(Containers)
容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。
算法(Algorithms)
算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。
迭代器(iterators)
迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。
高级
动态内存
运算符
new
但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
delete
操作
new data-type;
data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
pvalue = new char[20]; // 为变量请求内存
删除
删除数组 delete [] pvalue;
删除普通变量 delete pvalue;
命名空间
可作为附加信息来区分不同库中相同名称的函数、类、变量等。
本质上,命名空间就是定义了一个范围。
定义命名空间
使用关键字 namespace
namespace namespace_name { // 代码声明 }
调用命名空间
前面加上命名空间的名称
name::code; // code 可以是变量或函数
using 指令
using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称
using namespace std;
也可以用来指定命名空间中的特定项目
using std::cout;
在使用 cout 时就可以不用加上命名空间名称作为前缀,但是 std 命名空间中的其他项目仍然需要加上命名空间名称作为前缀
小知识
全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
模板
函数模板
含义
建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表
语法
template < 类型形式参数表 > 类型 函数名 (形式参数表) { //语句序列 }
我们也可以定义多个类型 template <typename T, typename T2>
template <typename T1,typename T2> static T1 Max(T1 a, T2 b) { return a>b? a:b; }
类模板
宏定义
运算符
#
转换为用引号引起来的字符串。
#define MKSTR( x ) #x
结果是MKSTR( x ) 会在编译前被转换成 字符串 x
MKSTR(156hhh) 变成 "156hhh" 放在代码中
#的用法是负责将其后面的东西转化为字符串
##
参数会被连接起来,并用来取代宏
万物互联,想连什么就在什么前面加两个##,不亦乐乎
#define concat(a, b) a ## b
#define IntVar(i) int_##i
#define CONCAT(a,b) 12 ##a ##b ##a
CONTCAT(45,67) 会转换成 12456745
注意##后面要紧跟前面的参数
##是连接符,将前后两个东西连接成一个词
实例
int main(){ int int_1 = 1, int_2 = 2; cout << IntVar(1) << ", " << IntVar(2) << endl; return 0; }
这段代码中IntVar(1)就会将前面的int_与参数1连接起来,成为int_1,然后由cout输出int_1变量对应的值1。对于IntVar(2)同理。
小点
this 指针是一个特殊的指针,它指向当前对象的实例。
int& r = i; & 读作引用。因此,第一个声明可以读作 "r 是一个初始化为 i 的整型引用
内联函数
在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
引入内联函数的目的是为了解决程序中函数调用的效率问题
编译器会将其内联展开, 而不是按通常的函数调用机制进行调用,相当于把代码抄过来,而不是留一个函数指针,等着指令的跳转
只有当函数只有 10 行甚至更少时才将其定义为内联函数.
对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联
引用传递参数
就是传递进来的形参就是实参变量的别名,对形参的修改都是直接修改实参,在类的成员函数中经常用到类的引用对象作为形参,大大的提高代码的效率
即可以少创建一个对象,在将实际参数压入栈是,不开辟新变量的内存空间,相当于指针引用
void Fun(int &a){ cout<<"形参引用a的地址 = "<< &a <<endl; a = 100; //对形参引用进行修改 }
其实我感觉,就是传递指针,但是不是显示传指针,用起来不用遵循指针的格式,而是直接按变量来用。
当引用传参是,也会开辟新的内存,存放原实参的地址,被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。相当于形参传进一个指针,你对形参的操作相当于对指针寻址,然后操作
参数
实参
实参全称叫做“实际参数”,顾名思义就是实际存在的参数,实参可以是常量、变量、表达式、类等,实参必须要有确定的值。
形参
形参全称叫做“形式参数”,也是一个虚拟的参数,在定义方法的时候使用的参数,形参是方法被调用时用于接收实参值的变量。
形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
基本
Lambda 函数与表达式
作用
#include <algorithm> #include <cmath> void abssort(float* x, unsigned n) { std::sort(x, x + n, // Lambda expression begins [](float a, float b) { return (std::abs(a) < std::abs(b)); } // end of lambda expression ); }
在上面的实例中std::sort函数第三个参数应该是传递一个排序规则的函数,但是这个实例中直接将排序函数的实现写在应该传递函数的位置,省去了定义排序函数的过程,对于这种不需要复用,且短小的函数,直接传递函数体可以增加代码的可读性。
表达式
[capture](parameters)->return-type{body}
[](int x, int y){ return x < y ; }
[](int x, int y) -> int { int z = x + y; return z + x; }
[this]() { this->someFunc(); }();
对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
[] // 沒有定义任何变量。使用未定义变量会引发错误。 [x, &y] // x以传值方式传入(默认),y以引用方式传入。 [&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。 [=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。 [&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。 [=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用
捕获的含义
在代码上下文中会有很多变量,如果在匿名(Lambda)函数中想要使用,一种方式是传参,但是如果参数太多就会显得很臃肿,所以留下了捕获这么一个接口。 空捕获:不需要上下文中的任何变量 [=]把当前所有变量的值都按传值方式引用,应该会占一些内存,而且这些传入的值是不可以变化的,相当于是一份拷贝。 [&]:所有变量都按引用方式引用,因为是引用,所以函数内可以实时更新变量的值