51、C++ 学习笔记

1 篇文章 0 订阅

一、C++语法

01、引用类型

        引用类型是C++引入的新类型,根据汇编的知识进行理解,程序在汇编后,变量名将失去意义,因为汇编码将替换成用内存的(链接地址or运行地址)访问变量。在C/C++语言中,用变量名表示变量所占的那块内存,为了使多个名称绑定一个变量,C++引入了引用类型。其主要目的就是让变量名和变量能够真正的区分开,C语言中变量名和变量是1对1的,是分不开的。
        int i = 1;    //栈中分配sizeof大小内存,内存里的值为1,i <=> 内存的标号。
        int &ri = i; //给变量取一个别名ri,ri <=> i。
注意:定义引用,并不是定义变量,因为定义引用是不开辟空间的。可以理解为定义变量名,所以它必须初始化,必须与变量绑定起来。

02、const限定符

        const 对象必须初始化:
                const int j = 42;             //正确(编译时初始化)
                const int i = get_size(); //正确(运行时初始化)
                const int i;                     //错误
        const 与普通类型的组合:
                const int 与int const是等价的
                const float 与 float const是等价的
                。。。。   
        const 与指针类型组合(仅指针):
                int c1 = 0;
                const int c2 = 0;
                const int *p1 = &c1;  //左侧const限定右边的变量,即c1不能改变,限定右边的叫低层const
                int * const p2 = &c2; //右侧const限定左边的变量,即p2不能改变,限定左边的叫顶层const
                const int *const p3 = p2;  //左右边的值都被限定,都不能改变
        const 与引用的组合:
                const int &ri = c2;  //仅此一种写法,且只限定右边的变量
        注意:const限定的都是变量,无论左边还是右边的变量都是左值,它并不针对右值。
        注意:左值和右值不是根据在 = 号左的右来区分的(百度上有很多根据等于号这样的错误说法)

03、命名空间的写法

写法一:在cpp文件中,每个变量的定义都加上命名空间的名称。

 写法二:在cpp文件中,每个变量的定义都用namespace框起来。

04、C++ STL中常用的数据结构

 1、C++标准库(STL)之顺序型容器
        vector:可变大小数组
        deque:双端队列
        list:双向链表
        forward_list:单向链表
        array:固定大小数组
        string:与vector相似,但专门用于保存字符
        =====>顺序容器适配器
        stack:栈适配器,默认情况下基于dequeue实现
        queue:队列适配器,默认情况下基于dequeue实现
        priority_queue:优先队列适配器,默认情况下基于vector实现
2、C++标准库(STL)之关联性容器
        =====>按关键字有序保存
        map:关联数组
        set:关键字即值,只保存关键字的容器
        multimap:关键字可重复出现的map
        multiset:关键字可重复出现的set
        =====>关键字无序保存
        unordered_map:用哈希函数组织的map
        unordered_set:用哈希函数组织的set
        unordered_multimap:哈希组织的map;关键字可以重复出现
        unordered_multiset:哈希组织的set;关键字可以重复出现

总结:宏定义可以摆脱变量类型,但不具备类型的检查。模板不仅可以拜托变量类型,还可以进行类型的检查。泛型编程作用举例:函数重载时,函数逻辑相同,但形参类型不同,要写不同的函数,有了函数模板,只需要写一个函数就行了。

//vector使用示例========================================
int main()
{
    std::vector<int> vec = { 1, 2, 3 };
    vec.insert(vec.begin(), 11);
    vec.push_back(99);
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << std::endl;
    }
    std::cout << "------------------------" << std::endl;
    for (auto& x : vec) {
        std::cout << x << std::endl;
    }
    return 0;
}
//vector<int>::iterator it = vec.begin(); //对指向的内容可读可写
//vector<int>::const_iterator it = vec.begin(); //对指向的内容只能读

05、OOP的三大特征之类的封装

0、这里主要说明类的构造函数和初始化参数列表。
1、类的缺省构造函数

#include <iostream>
class Point{
public:
    int x;
    int y;
};
int main(int argc,char* argv[]){
    Point pt;  //调用缺省构造函数
    //Point pt();
    pt.x = 0;
    pt.y = 1;
    return 0;
}

==>说明:在不定义构造函数,会生成一个缺省构造函数,如下所示
class Point{
public:
    Point(){
       //一些默认初始化操作
    };
    int x;
    int y;
};

 2、一旦你定义构造函数,将不再生成缺省构造函数

#include <iostream>
class Point {
public:
    Point(int j, int k) {
        this->x = j;
        this->y = k;
    }
    int x;
    int y;
};
int main(int argc, char* argv[]) {
    //Point pt;     //错误,没有缺省构造函数
    Point pt(5, 5); //正确
    pt.x = 0;
    pt.y = 1;
    std::cout << pt.x << std::endl;
    std::cout << pt.y << std::endl;
    return 0;
}

=>说明:C++11允许我们使用=default来生成一个默认的构造函数
class Point {
public:
    Point() = default;
    Point(int j, int k) {
        this->x = j;
        this->y = k;
    }
    int x;
    int y;
};

  3、初始化参数列表

1、定义时初始化
class Point {
public:
    Point() = default;
    Point(int _x, int _y) : x(_x), y(_y) {
        
    }
    int x = 0;
    int y = 0;
};
2、定义后初始化
class Point {
public:
    Point() = default;
    Point(int _x, int _y){
        this->x = _x;
        this->y = _y;
    }
    int x = 0;
    int y = 0;
};
=>总结:初始化列表:
    对类的成员进行:定义时初始化
    初始化列表仅适用于构造函数(初始化列表是初始化成员变量的)

06、OOP的三大特征之类的继承

0、这里主要说明父类的构造函数和构造函数执行的顺序。
1、父类无缺省构造函数时,要显式调用父类的构造函数:

#include <iostream>
using namespace std;
class A {
public:
    A(int y) {
        std::cout << "A()" << std::endl;
    }
    ~A() {
        std::cout << "~A()" << std::endl;
    }
};

class Point: public A{
public:
    Point(int j, int k):A(3){  //显式调用父类的构造函数
        this->x = j;
        this->y = k;
        std::cout << "Point(int j, int k)" << std::endl;
    }
    int x;
    int y;
};
int main(int argc, char* argv[]) {
    Point pt(1, 2);
    return 0;
}

2、C++的初始化方式:直接初始化、拷贝初始化
      直接初始化:            =>调用构造函数的初始化
                int i(5);  //类中不可用
                int i{5};
                int i = int(5);
      拷贝初始化:            =>调用拷贝函数的初始化
                int i = 1;    //需要注意的是,这里发生了隐式类型转换(直接初始化是显式的、其实在C++中写成隐式的类型转换是不安全的,不利于代码阅读,建议养成使用显式的习惯)
需要注意的是:类是C++的相对于C的新增部分,其在类内成员变量初始化问题上,有一个规定,即类内部的()全被解释为函数调用,所以类内只能用=和{}进行初始化,而不能使用()。
3、基类的实例化顺序:
        1、实例化成员变量。
        2、执行类的构造函数。
4、子类的实例化顺序:先按继承顺序实例化每个父类,最后再实例化子类。(析构相反)

基类实例化 => 子类实例化 => 子类析构 => 基类析构

示例程序:

#include <iostream>
using namespace std;
class A {
public:
    A(int a) { cout << "A():id = "<< this << endl; }
    ~A() { cout << "~A():id = " << this << endl; }
};
class B {
public:
    B(int b) { cout << "B():id = " << this << endl; }
    ~B() { cout << "~B():id = " << this << endl; }
    A obj{ 2 };
};
class Point :public B, A {
public:
    Point(int x, int y):A(2),B(3) { cout << "Point():id = " << this << endl; }
    ~Point() { cout << "~Point():id = " << this << endl; }
};
int main(int argc, char* argv[]) {
    Point pt(5, 5);
    return 0;
}

打印如下:  先实例化B => 再实例化A => 最后实例化Point
        A():id = 005AFBE4          // B
        B():id = 005AFBE4          // B
        A():id = 005AFBE6          // A
        Point():id = 005AFBE4    //Point
        ~Point():id = 005AFBE4
        ~A():id = 005AFBE6
        ~B():id = 005AFBE4
        ~A():id = 005AFBE4

07、OOP的三大特征之类的多态

0、C++允许基类类型的指针指向子类的对象。
1、C++中,主要有两种类型的多态性:
        编译时多态性(静态多态性):
                函数重载、运算符重载、模板。
        运行时多态性(动态多态性):
                虚函数、多态继承。
2、主要是动态多态性:同一操作总用于不同的对象,可以有不同的执行结果
      实现方式:基类的虚函数、子类函数重写(覆盖)、基类指针
      关键字:基类中定义virtual函数、子类中重写并添加override,(override可以不加,但建议加上,因为加上override不仅可以进行规则检查,还可以增加代码的可读性)

#include <iostream>
class Shape {
public:
  virtual void draw() {
    std::cout << "Drawing shape...\n";
  }
};

class Rectangle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing rectangle...\n";
  }
};

class Circle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing circle...\n";
  }
};

int main() {
  Shape* shapes[2];
  shapes[0] = new Rectangle();
  shapes[1] = new Circle();

  for (int i = 0; i < 2; i++) {
    shapes[i]->draw();
  }

  return 0;
}

3、抽象类:除了使用虚函数,C++ 中还可以使用抽象类实现多态。抽象类是一种不能被实例化的类,它通常包含至少一个纯虚函数,纯虚函数在基类中声明时没有函数体,在派生类中必须被重写。由于抽象类不能被实例化,因此派生类必须实现所有纯虚函数才能被实例化

#include <iostream>
class Shape {
public:
  virtual void draw() = 0;
};

class Rectangle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing rectangle...\n";
  }
};

class Circle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing circle...\n";
  }
};

int main() {
  Shape* shapes[2];
  shapes[0] = new Rectangle();
  shapes[1] = new Circle();

  for (int i = 0; i < 2; i++) {
    shapes[i]->draw();
  }

  return 0;
}

08、C++ 初始化与赋值的区别

        初始化的含义是在创建对象时赋予一个初值。
        赋值的含义是将对象的当前值擦除掉,以一个新值代替。        
        区分方式:在对象创建时赋予值,叫初始化,否则即为赋值。
        C++语言的初始化方式(以类类型为例)
                <1>:直接初始化:调用的是与实参匹配的构造函数
                <2>:拷贝初始化:调用的是拷贝构造函数 

09、C++ 泛型编程的基础-模板

0、面向对象编程和泛型编程都是一种编程范式。
1、C++ Primer(第五版):面向对象编程(OPP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:
        OPP能处理类型在程序运行之前都未知的情况。(运行时知类型)
        泛型编程中,编译时就能获得其类型了。(编译时知类型)
2、模板和宏定义很像,但是二者不同,例如:宏定义有直接替换而不检查的缺陷,而模板没有此缺陷。具体可以参考匿名类或结构体的使用。(类似但不同,模板更安全=>非替换=>根据参数反推实例化类型)
3、编译过程无法区分类是不是被实例化过:所以会被编译进可执行文件中。
      编译过程可以区分类模板是不是被实例化过:所以会选择性的编译进可执行文件中。(类模板的转换发生在编译期间,如何编译期间未被实例化过,就不会被编译进可执行文件中)
4、                                          类定义==实例化==>对象    (类的实例化发生在运行期间)
        类模板定义==实例化==>类定义==实例化==>对象    (类模板的实例化发生在编译期间)

5、代码示例

#include <iostream>
//1、auto:
//     此关键字也属于泛型编程的范畴
//2、模板:
//    输入可以是类型,也可以是变量
//    当模板的参数只有类型时,可以不指定类型,编译器根据输入推出类型T、生成对应的函数。   
//    当模板的参数出现变量时,无论有没有类型,所有的模板的输入都必须手动指定。
//其它:定义模板函数,建议使用typename声明类型,定义模板类时,建议使用class声明类型。   
template<typename T, int N> 
int fun(const T param) {
    return param + (T)N;
}
int main() {
    cout << fun<int, 2>(3) << endl;
    return 0;
}

二、C++算法

前言:子集问题很重要,对数组来说,要熟练学会求数组的组合。

01、排序算法

02、二分查找

题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
    如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
    请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
    输入: nums = [1,3,5,6], target = 5
    输出: 2
示例 2:
    输入: nums = [1,3,5,6], target = 2
    输出: 1
示例 3:
    输入: nums = [1,3], target = 2
    输出: 1

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0,right = n-1,mid;
        while(left<=right){
            mid = left+((right-left)>>1);
            if(target == nums[mid])   return mid;
            else if(target>nums[mid]) left=mid+1;
            else                      right=mid-1;
        }
        return left;
    }
};

03、枚举连续子集

#include <iostream>
#include <vector>
//按同一起点枚举开始枚举,长度依次递增
vector<vector<int>> findSubNum1(vector<int> &nums, int n){
    vector<vector<int>> twod_nums;
    for(int left=0; left<n; left++){
        for(int right=left; right<n; right++){
            vector<int> vec(nums.begin()+left, nums.begin()+right+1);
            //do sthing
            twod_nums.push_back(vec);
        }  
    }
    return twod_nums;
}
//按同一阶数开始枚举,长度依次递增
vector<vector<int>> findSubNum2(vector<int> &nums, int n){
    vector<vector<int>> twod_nums;
    for(int len=1; len<=n; len++){
        for(int left=0; left<=n-len; left++){
            int right = left + len -1;
            vector<int> vec(nums.begin()+left, nums.begin()+right+1);
            //do sthing
            twod_nums.push_back(vec);
        }  
    }
    return twod_nums;
}

int main() {
    vector<int> nums = {2,1,5};
    vector<vector<int>> twod_nums = findSubNum1(nums, nums.size());
    //vector<vector<int>> twod_nums = findSubNum2(nums, nums.size());
    for (auto v : twod_nums) {
        for (auto num : v) {
            cout << num << ',';
        }
        cout << endl;
    }
}

=>findSubNum1输出           =>findSubNum2输出
2,                          2,
2,1,                        1,
2,1,5,                      5,
1,                          2,1,
1,5,                        1,5,
5,                          2,1,5,

04、寻找最长连续字符子集

举例:string str = "GRAAHHCCDEKFF";(无小写字母),的最长连续字符子集为"CDEFGH".
初始:排序 => str = "AACCDEFFGHHKR", ?求最长连续字符子集

//查找一个字符串的连续字串中的连续字符最长的子串
vector<string::iterator> findMaxLength(string& s) {
    auto pre_left = s.begin(), pre_right = pre_left;
    auto cur_left = s.begin(), cur_right = cur_left;
    int max_dif = 0;
    while (cur_right != s.end()) {
        cur_right++;
        if ((cur_right == s.end()) || (*cur_right!=*(cur_right-1) + 1)) {
            int dif = cur_right - cur_left;
            if (dif > max_dif) {
                max_dif = dif;
                pre_left = cur_left;
                pre_right = cur_right - 1;
            }
            cur_left = cur_right;
        }
    }
    return{ pre_left, pre_right };
}

05、二叉树问题

0、二叉树的种类:
        1、满二叉树:最后一层全是叶子节点,其他层全是二叉节点
        2、完全二叉树:除了最后一层,每一层都是满的,且最后一层的节点从左到右依次填充。
        3、平衡二叉树:左子树和右子树的高度差不能大于1
2、二叉树的高度与深度:



         

06、贪心算法(局部最优推全局最优)

07、回溯算法(解决排列组合相关问题)

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。

示例 1:
输入:n = 4, k = 2
输出:
[ [2,4],[3,4],[2,3],[1,2],[1,3],[1,4] ]

示例 2:
输入:n = 1, k = 1
输出:[[1]]

class Solution {
public:
    vector<vector<int>> vec_sum;
    vector<int> vec_sub;
    void backtracking(int n, int k, int begin){
        if(vec_sub.size()==k){
            vec_sum.push_back(vec_sub);
            return;   
        }
        for(int i=begin; i<=n-(k-vec_sub.size())+1; i++){ 
            vec_sub.push_back(i);  //装vec[begin]
            //装vec[begin+1],(装vec[begin+2]时=k满足,故没装成;=>pop pop;
            //保存结果并返回,此轮结束。
            backtracking(n,k,i+1); 
            vec_sub.pop_back();    //撤销选择
            //每次开始时vec_sub的长度都为0
        }
    }
    vector<vector<int>> combine(int n, int k) {
        if(k<=0||n<k){ return {{}}; }
        backtracking(n,k,1);
        return vec_sum;
    }
};



 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值