C++学习记录

求π

马青公式 π = 16arctan1/5 - 4arctan1/239 ,由英国天文学教授约翰·马青于1706年发现。
在这里插入图片描述

  double a = 16.0 * arctan(1.0 / 5.0);
  double b = 4.0 * arctan(1.0 / 239.0);
  double pi = a - b;

arctan 计算

采用泰勒公式
在这里插入图片描述

double arctan(double x) {  //泰勒
  double head = x;
  int tail = 1;
  double art = 0;
  while (double(head / tail) > 1e-15) {
    art = (tail % 4 == 1) ? art + head / tail : art - head / tail;
    head *= x * x;
    tail += 2;
    cout << "---------------" << endl;
    cout << tail << endl;
    cout << "---------------" << endl;
  }
  return art;
}

汉诺塔

汉诺塔问题的源于印度一个古老传说的益智玩具。大焚天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照先大后小的顺序摞着64片圆盘。大焚天命令婆罗门把圆盘从下面按大小顺序重新摆放在另一根柱子上,并且规定在小盘子上不能放大盘子,在三根柱子之间一次只能移动一个盘子。
编程模拟该问题,从柱子A移动到柱子C。

int main(int argc, char const *argv[]) {
  cout << "请输入盘子数量:";
  int disks;
  cin >> disks;
  hanoi(disks, 'A', 'B', 'C');

  return 0;
}

void move(char A, char B){
    cout << A << "->" << B<<endl;
}

void hanoi(int n, char A, char B, char C){
    if(n==1){
        move(A,C);
    }
    else{
        hanoi(n-1,A,C,B);
        move(A,C);
        hanoi(n-1,B,A,C);
    }
}

静态

静态变量:静态局部变量,有全局寿命,局部可见。
静态函数:使某个函数只在一个源文件中有效,不能被其他源文件所用
静态数据成员:该类的所有对象维护该成员的同一个拷贝。必须在类外定义和初始化,用(::)来指明所属的类。类外初始化
静态成员函数:可以用类名直接调用而不用实例化,但只能访问静态数据成员。
以上都是用关键字static声明

内联


需要在变量前加扩号,整体再加括号
如 #define ADD(x,y) ((x)+(y))
内联是c++特性,更为方便,由编译器在调用内联函数的地方直接展开,省去了函数压栈的开销。
内联用法:在函数的最前面加 inline

inline int add(int a, int b)
{
	return a + b;
}

内联成员函数 参考:C++实操 - 内联成员函数

const

成员函数后面加const作用,例如
float Circle::Circumference() const { return 2 * PI * radius; }
表明参数列表里省略的this指针是常量指针。
const可以用于重载成员函数,由const修饰的成员函数只能常量对象调用

class R{
	...
	void print();
	void print() const;
}
...
R a;
const R b;
a.print()//调用的时void print();
b.print()//调用的是void print() const;

常量指针: 指向常量的指针 const int * p 或 int const *p
指针常量: 指针本身是常量 int *const p
tips :从右向左看变量的修饰,离变量最近的才是本质。比如int * const p,离p最近的是const,因此是指针常量。
又如 int *& r = p ; r首先是引用,引用的是指针,因此 r 是指针p的引用。注意引用本身不是对象,因此不存在指向引用的指针。不能写成 int & * r。

指针和引用

引用相当于指针常量,并自动被编译器解引用
参考 深入分析C++引用(指针常量)

类的前向引用

参考C++面向对象 - 类的前向声明的用法

构造函数初始化

构造函数:Time(int h = 0, int m = 0, int s = 0){ hh = h; mm = m, ss = s}
初始化列表构造函数 Time(int h = 0, int m = 0, int s = 0) : hh(h), mm(m), ss(s) {}
tips:初始化顺序与初始化列表顺序无关,只与内存中顺序(类中变量声明顺序)有关。C++学习之路-构造函数的初始化列表
构造函数执行顺序:
先构造成员对象
再构造自身(调用构造函数)
析构函数顺序与上述顺序,构造后入栈,以出栈顺序析构。

运算符重载

自增运算符++、自减运算符–都可以被重载,但是它们有前置、后置之分。但用operator ++ ()重载后,不论obj++还是++obj,都等价于obj.operator++(),无法体现出差别。为了解决这个问题,C++ 规定,在重载++或–时,允许写一个增加了无用 int 类型形参的版本,编译器处理++或–前置的表达式时,调用参数个数正常的重载函数;处理后置表达式时,调用多出一个参数的重载函数。如:

//默认的前置++
Time Time::operator++() {
  ++second;
}
//后置++,副本保存当前值,调用前置++,并返回副本
Time Time::operator++(int n) {
  Time tmp = *this;
  ++(*this);
  return tmp;
}

属性(权限)

成员属性
public :类的public成员可以被任意实体访问,可以直接用a.x这种形式访问;
private: 类的private成员不能直接被类的实体访问,也不能被子类的实体访问,但是可以被类的成员函数访问;
protected:类的protected成员不能直接被类的实体访问,但是可以被子类访问,也可以被类的成员函数访问;
class 首先定义的未指定属性的成员默认为private;protected与private 基本一致,区别在于protected成员可以被子类访问而private不能。
继承方式
public :父类的public,protected成员属性不变,父类private成员不可访问。
proteted :父类public ,protected 成员都变为protected成员,父类private成员不可访问。
private:父类public ,protected 成员都变为private成员,父类private成员不可访问。

类与派生类的关系

基类对象可接受派生类对象赋值。
基类指针可指向派生类对象的地址。
基类对象可引用派生类对象。
反之则不行。

派生类构造函数

若基类的构造函数有参数,则需要在子类的构造函数列表中调用父类构造函数传入参数。当有多个基类时,构造函数将按照继承方式中的生命调用。class C : public B,public A {}。与它们在构造函数初始化列表中的次序无关。C(int a,int b):A(a),B(b){}
当有多个对象成员时,将按它们在派生类中的声明次序调用,与它们在构造函数初始化列表中的次序无关。
当构造函数初始化列表中的基类和对象成员的构造函数调用完成之后,才执行派生类构造函数体中的程序代码。
当同时存在直接基类和间接基类时,每个派生类只负责其直接基类的构造。

虚拟继承

多继承可能导致冲突,比如菱形继承。因此用虚拟继承解决以上问题,参考
彻底弄懂C++虚拟继承
虚拟继承必须由最后的派生类调用虚基类的构造函数来初始化虚基类部分

继承成员函数内部变化

虚函数允许用基类指针调用子类函数,同时在虚函数在基类函数中调用,也是调用的子类版本

/* 从基类继承的成员将访问到派生类版本.cpp */
#include <iostream>
using namespace std;
class B {
public:
  void f() { g(); }
  virtual void g() { cout << "B::g"; } //若将virtual去掉
};
class D : public B {
public:
  void g() { cout << "D::g\n"; }
};
int main() {
  D d;
  d.f();
  
  return 0;
}
//put :  D:g
//若将virtual去掉 ,put :  B:g

虚析构函数

在很多时候,会使用父类类型的指针,指向用new动态生成的子类对象。按照谁申请谁释放的原则,将父类类型指针使用delete运算符释放内存的时候,此时因为指针类型是父类类型,所以只会调用父类的析构函数,而不会调用子类析构函数,这会导致很多问题(包括但不限于):
① 内存可能并未完全释放:子类所占的内存空间会比父类更大,delete父类指针的时候,可能会导致子类部分的内存泄漏,比如子类中有动态申请内存的操作的情况下,会导致堆内存泄漏(当然,如果程序员没有在析构的时候释放内存,即使析构函数执行了,还是会有内存泄漏)。
② 逻辑上的错误:比如,一个类采用了引用计数来统计类的对象,那在析构的时候,应该对这个引用计数进行处理,delete父类指针的时候,没有调用到子类析构函数,会导致引用计数错误。
析构函数声明为虚函数,delete父类类型指针时,使得子类析构函数得以调用
需要特别注意的是:析构函数可以被声明为虚函数,构造函数不可以。

class A
{
public:
    // 析构函数声明为虚函数,使得子类析构函数得以调用
    virtual ~A() { cout << "Destroy A\n"; }
};
class B : public A
{
public:
    // 父类析构函数是虚函数,在析构完子类后会调用父类析构函数
	~B() { cout << "Destroy B\n"; }
};

int main()
{
    A* p = new B();
    // 这里需要注意会先调用子类析构函数,再调用父类析构函数:
    // 输出 Destroy B
    // 输出 Destroy A
    delete p;
    p = nullptr;
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值