使用Clion编译器,C++11
头文件与类的声明
引用头文件:如果是系统的头文件用<>,如果是自己编写的头文件用""。
延伸文件名不一定是.h或者.cpp,可能是.hpp或其他或无延伸名
比如下面的程序,如果写成iostream.h反而报错
#include <iostream>
#include "my.h"
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
防卫式声明guard:
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
类的声明:
complex.h:有些函数在此直接定义,有些函数只是声明
//
// Created by l30035748 on 2022/8/4.
//
#ifndef COMPLEX_COMPLEX_H
#define COMPLEX_COMPLEX_H
class complex{
public :
complex (double r = 0,double i = 0)
: re(r),im(i)
{}
complex& operator += (const complex&);
double real()const {return re;}
double imag()const {return im;}
private:
double re, im;
friend complex& __doapl(complex *,const complex&);
};
#endif //COMPLEX_COMPLEX_H
inline内联函数
函数在类的本体中定义,自动成为inline函数,效率很高
但是函数太复杂就没办法inline,是否是inline由编译器决定
关键字 inline
访问级别
public、private、protected
构造函数
创建对象时系统自动调用
complex (double r = 0,double i = 0) // r和i的默认值是0,也就是如果创建时候不传参,默认就是0,0
: re(r),im(i) //初始化列表,只有构造函数才有,推荐构造函数使用这种方式
{}
在括号里写也完全ok,但是效率会低,原因如下:构造会有两阶段,第一阶段是初始化,第二阶段是赋值,如果在括号里写,就相当于放弃第一阶段的初始化,而等到第二阶段才来设值。
函数重载
构造函数可以有overloading重载
参数传递和返回值
常量成员函数
const member functions
doublie real() const{return re;}
const complex c1(1,2);
cout << c1.real(); //如果上面方法不是const的,现在complex又是const的,则该方法调用失败,因为方法可修改,但是用户认为c1不可修改,两相矛盾
加了const则代表不能修改变量
参数传递
值传递和引用传递(是否const)
complex (double r = 0,double i = 0)
: re(r),im(i)
{}
complex& operator += (const complex&); // complex& 代表引用,加const代表常量引用
尽量不要传值,因为值可能很大,传引用(底部就是指针,四个字节),不希望别人修改值就加const。
返回值传递
返回值或者返回引用(是否const)
complex& operator += (const complex&); //返回引用
double real()const {return re;} //返回值
friend 友元
private:
double re, im;
friend complex& __doapl(complex *,const complex&);
inline complex& __doapl(complex* ths,const complex& r) //自由获取friend的private成员
{
ths->re += r.re;
ths->im += r.im;
return *this;
}
正常来说private里面的数据是不允许被类外访问,但是加了friend之后,friend函数就可以直接拿private里面的数据
friend会破坏封装,也可以利用函数进行访问,但是效率会低一点。
相同class 的各个objects 互为friend(友元)
complex类中方法可以看出来
int func(const complex& param)
{return param.re + param.im;}
什么时候可以引用传递,什么时候可以返回引用
inline complex& __doapl(complex* ths,const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *this;
}
//下面这种不能返回引用,因为temp是临时值,如果是返回引用,函数结束后temp会被回收,引用指向的地址就有问题
inline complex& __doapl(complex* ths,const complex& r)
{
complex temp(0,0);
temp.re = ths->re + r.re;
temp.im = ths->im + r.im;
return temp;
}
操作符重载和临时对象
inline complex& __doapl(complex* ths,const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex& complex::operator+=(const complex& r) {
return __doapl(this,r);
}
返回引用语法分析
传递者不需要知道接收者是以引用形式接收
也就是返回可以是值,接收值的人声明是引用类型也不会有问题
inline complex& __doapl(complex* ths,const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths; //传递者返回值
}
操作符重载2,非成员函数
上面的+=操作符重载中,函数定义写了complex::代表是complex的成员函数
下面写三个非成员函数的操作符重载
inline complex operator+ (const complex& x, const complex& y)
{
return complex( real(x) + real(y),imag(x) + imag(y) );
}
inline complex operator+ (const complex& x, double y)
{
return complex( real(x) + y,imag(x));
}
inline complex operator+ (double x, const complex& y)
{
return complex( x + real(y),imag(y) ); //临时对象,格式typename();
}
与成员函数的区别是成员函数有this指针,全局函数没有,必须两个参数都标明
上述函数绝对不能返回引用,因为他们返回的是一个本地对象,放在函数栈中,函数结束栈就销毁这个对象,所以不能传引用
正负号函数重载如下
inline complex operator + (const complex& x)
{
return x;
}
inline complex operator - (const complex& x)
{
return complex(-real(x), -imag(x));
}
三大函数Big Three:拷贝构造、拷贝赋值、析构
如果不写拷贝构造,系统会默认创建一套拷贝构造的函数,但是对于指针来说,会导致两个变量指向同一个空间,所以我们必须自己实现拷贝构造和拷贝赋值。(浅拷贝和深拷贝)
string.h
//
// Created by l30035748 on 2022/8/4.
//
#ifndef __MYSTRING__
#define __MYSTRING__
#include <cstring>
#include <iostream>
using namespace std;
class String
{
public:
String(const char* cstr = 0);
String(const String & str); // 拷贝构造
String& operator= (const String& str); // 拷贝赋值
~String(); // 析构函数,当对象死亡时,会调用该函数
char* get_c_str() const {return m_data; }
private:
char * m_data;
};
//普通构造
inline String::String(const char* cstr)
{
if(cstr){
m_data = new char[strlen(cstr) + 1];
strcpy(m_data,cstr);
}
else{ //未指定初始值,此时cstr = 0
m_data = new char [1];
*m_data = '\0';
}
}
//拷贝构造
inline String::String(const String & str){
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data,str.m_data);
}
//拷贝赋值
inline String& String::operator= (const String& str)
{
if(this == &str)
return *this;
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data,str.m_data);
return *this;
}
//析构函数
inline String::~String() {
delete[] m_data;
}
//<<重载,不能写成成员函数,这样会导致str << out;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif //__MYSTRING__
堆、栈与内存管理
stack栈:存在于某作用域(scope)的一块内存空间。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本体内声明的任何变量,其所使用的内存块都取自上述stack。
heap堆:指由操作系统提供的一块global内存空间,程序可动态分配,从其中获得若干区块。
栈对象生命在作用域运行时,当作用域结束之后,就会自动调用析构函数释放内存
static对象生命在作用域结束后依然存在,直到整个程序结束。
全局对象生命在整个程序结束后才结束。
堆对象在new之后创建,在用完记得delete,不然指针死亡而空间还在,会导致内存泄漏。
new:先分配空间(malloc),再类型转化,再调用构造
delete:先调用析构函数,再释放内存(free)
动态分配所得的内存块细节看视频
array new 一定要搭配array delete
扩展补充:类模板、函数模板以及其它
static
独一份
调用static函数的方式有两种:
- 通过object调用
- 通过class name调用
#include "iostream"
using namespace std;
class Account{
public:
static double m_rate;
static void set_rate(const double& x){m_rate = x;};
};
double Account::m_rate = 8.0;
int main(){
Account::set_rate(5.0); // 2.
Account a;
a.set_rate(7.0); // 1.
cout << a.m_rate << endl;
}
static 实现单例的两种方式
class A{
public:
static A& getInstance(){return a;};
private:
A();
A(const A& rhs);
static A a;
};
class A1{
public:
static A1& getInstance();
private:
A1();
A1(const A1& rhs);
};
A1& A1::getInstance() {
static A1 a1;
return a1;
}
cout
底层就是集成自ostream
类模板
template<typename T>
class complex
{
public:
complex(T r = 0,T i =0 ) :re(r), im(i){};
complex& operator += (const complex&);
T real() const {return re;}
T imag() const {return im;}
private:
T re,im;
friend complex& __doapl(complex* , const complex&);
};
用的时候
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
函数模板
template<class T>
inline const T& min(const T& a,const T& b)
{
return b < a ? b : a;
}
用的时候不必指明类型,编译器会对函数模板进行引数推导(argument deduction)
int a = 2,b = 3;
a = min(a,b);
namespace
//创建自己的命名空间
namespace my
{
}
//使用方法一:直接使用整个命名空间
#include "iostream"
using namespace std;
int main()
{
int a;
cin >> a ;
cout << "..." << endl;
}
//使用方法二:使用声明
#include "iostream"
using std::cout;
int main()
{
int a;
std::cin >> a ;
cout << "..." << std::endl;
}
//使用方法三:不使用命名空间
#include "iostream"
int main()
{
int a;
std::cin >> a ;
std::cout << "..." << std::endl;
}
组合与继承(面向对象)
Composition复合,表示has-a
同步创建
class Container{
protected:
Component com;
}
构造由内而外
Container的构造函数首先调用Component的default构造函数,然后才执行自己。
编译器编译时:Container::Container(…) : Component(){…};
析构由外而内
Container的析构函数首先执行自己,然后才调用Component的析构函数
Container::~Container(…){… ~Component() };
Delegation委托,Composition by reference(引用组成)
不同步创建,内部成员是一个指针
Handle / Body
class String{
...
private:
StringRep* rep; // pimpl
}
//。。。
class StringRep{
...
}
Inheritance继承,表示is-a
三种继承方式:public protected private
class Father
{
...
}
class Child : public Father
{
...
}
子类的类中有父类的成分
构造由内而外
子类先调用父类的构造函数,再执行自己的构造函数
析构由外而内
子类先调用自己的析构函数,然后才调用父类的析构函数
父类的析构函数必须是virtual的,否则会出现undefined behavior
虚函数与多态
虚函数
当我们使用继承的时候,我们需要搭配虚函数来使用,效果更好。
non-virtual函数:不希望子类重新定义函数(override,覆盖)
virtual函数:希望子类重新定义函数,而且现在这函数已经有默认定义
pure virtual函数:希望子类一定要重定义函数,现在对这函数没有默认定义
#include <iostream>
class Shape
{
public:
virtual void draw()const = 0; //纯虚函数 pure virtual
virtual void error(const std::string& msg); //虚函数
int objectID()const; //非虚函数
};
void Shape::error(const std::string &msg) {
std::cout << "默认错误" << std::endl;
}
class Rectangle : public Shape
{
public:
void error(const std::string &msg) override;
void draw() const override;
};
void Rectangle::error(const std::string &msg) {
std::cout << "子类错误" << std::endl;
}
inline void Rectangle::draw() const { //
std::cout << "aa只能子类实现" << std::endl;
}
虚函数还需多看一下具体语法