C++之零碎知识点记录

最近在复习之前学过的c++课程,整理下c++中一些零碎的知识点。

1. setprecision()为了精确小数位数,setiosflags(ios::fixed)为了保证当只有一位小数位且该小数位是0时,避免用e的指数输出。
cpp 复制代码
#include <iostream>
#include <iomanip>
using namespace std;

int main(){
    double cost=10.0;
    cout << setiosflags(ios::fixed) << setprecision(1) << cost << endl;
}
# 10.0   如果不设置setiosflags,输出是1e+01
2. c++中表示次方
cpp 复制代码
#include<cmath>

pow(height, 2)  # height^2
3. switch...case语句,可以指定连续的case(3 4 5),也可以指定2个场景case(6 7)
cpp 复制代码
 switch(month){
   case 3 ... 5: cout << "春季" << endl;  break;
   case 6:case 7: cout << "夏季" << endl;  break;
    ...
}
4. cin>>s1这种方式输入,会自动去除开头的空白符号(空格、换行、制表符等),从第一个真正的字符开始读取,遇到下一个空白时结束。getline(cin, s1)读取一整行,遇到换行符结束。
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
        
    string s1, s2;
    // getline(cin, s1);
    // getline(cin, s2);
    cin >> s1 ;
    cin >> s2;
    cout << s1+s2 << endl;

    return 0;
}
# 如果输入是"   hello to\n world",那当前输出只会是"helloto";用getline才能保证输出是" 
   hello to world"

如果是用char数组定义的字符串,则是以下用法:

cpp 复制代码
char str[100] = { 0 };
cin.getline(str, sizeof(str));  # 写入字符数组
5. char定义的字符数组,数组名指向第一个字符,但是cout打印数组名,编译器会自动输出后面的字符,在遇到'\0'后停止。字符数组名不能直接赋值,str2=str1是错误的,得用strcpy(str2, str1)。
cpp 复制代码
char str[20] = "hello world";
cout << str;

char *p = NULL;
p = &str[6];
cout << p;  # world
6. c++中数组名不能直接赋值,如arr2=arr1这样,数组名代表数组首地址,可以cout打印,输出数组首地址(str[]例外),数组想要打印出[1,2,3]这样的样式,只能自己去拼接[] 和逗号;

但是结构体可以struct2=struct1,但却不能cout打印;类对象也可以obj1=obj2,但不能cout打印。

7. 统计字符串长度:

如果是char a[] = xxx的定义方式,通过strlen(str1)获取,需要include<cstring>;

如果是string b=xxx的定义方式,通过b.size()或length(b)获取,需要include<string>;

以上统计都不包括末尾的'\0'字符,而sizeof()计算的是所占字节数,是包括'\0'字符的。

cpp 复制代码
char str1[] = "hello world";
sizeof(str1)     # 12
strlen(str1)     # 11
char str2[20] = "hello world";
sizeof(str2)     # 20
strlen(str2)     # 11
8. 统计字符子串出现的次数
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

int main() {

    char str[100] = { 0 };
    char substr[100] = { 0 };

    cin.getline(str, sizeof(str));
    cin.getline(substr, sizeof(substr));

    int count = 0;

    //转化为字符串
    string str1(str);
    string str2(substr);
    
    int i=0;
    //从str1下标i开始查找str2
    while(str1.find(str2,i)!=-1){
        //如果找得到,计数加1
        count++;
        //i从找到的位置,后移一位
        i=str1.find(str2,i)+1;
    }

    cout << count << endl;

    return 0;
}
9. 字符判断函数
字符判断函数 作用
isalpha() 判断字符是否是字母('a'-'z' 'A'-'Z')
isdigit() 判断字符是否是数字
isspace() 判断字符是否是空格、制表符、换行等标准空白
isalnum() 判断字符是否是字母或者数字
ispunct() 判断字符是标点符号
islower() 判断字符是否是小写字母('a'-'z')
isupper() 判断字符是否是大写字母('A'-'Z')
cpp 复制代码
string str1="hell123";
isdigit(str1[3]);
10. 函数中不能返回局部变量的引用或局部变量的地址。
cpp 复制代码
#include <iostream>
using namespace std;

int * func(){
    int a = 10;  // 局部变量存放在栈区,栈区内存由编译器自动释放,函数执行完就会释放
    int * b = &a;
    return b;
}
//这里返回局部变量地址会报错
int main(){
    int a = 5;
    int *par = func();
    cout << *par << endl; //第一次能打印成功,是因为编译器做了保留,其实这块内存已经被释放了
    cout << *par << endl; //第二次打印是乱码
}
cpp 复制代码
#include<iostream>
using namespace std;
//1、不要返回局部变量的引用 

string& test(){
    string a = "hello";  //局部变量存放在栈区,如果返回局部变量的引用,则函数定义就会报错
    return a;
}

如果将上述代码的string &改为string, 则不会有问题,因为这样返回的其实是a的备份数据,而不是局部变量本身。

11. 深拷贝的使用

如果定义的类中,有属性需要在堆区开辟内存,则一定要提供拷贝构造函数,重新申请堆区内存保存属性,还有如果使用涉及=赋值(obj1=obj2),则一定要重载=操作符,否则都会报错重复释放。

cpp 复制代码
#include<iostream>
using namespace std;

class Person{
    public:
        Person(int age){
            this->age = new int(age);
        }

        Person(const Person & p){
            this->age = new int(*p.age);
        }

        Person& operator=(Person &p){
            //应该先判断是否有属性在堆区,如果有,先释放干净,再重新申请堆区内存
            if(this->age != nullptr){
                delete this->age;
                this->age = nullptr;
            }
     
            this->age = new int(*p.age);
            // return p; 用下面的*this也可以
            return *this;
        }

        ~Person(){
            if(this->age != nullptr){
                delete this->age;
                this->age = nullptr;
            }
        }
        int *age;
};

int main(){
    Person p1(10);
    Person p2(20);
    Person p3(30);
    p3 = p2 = p1;
    cout << "p1.age = " << *p1.age << endl;  # 10
    cout << "p2.age = " << *p2.age << endl;  # 10
    cout << "p3.age = " << *p3.age << endl;  # 10
}
12. 虚继承、(纯)虚函数、(纯)虚析构

虚继承一般用于处理多继承的关系,尤其是菱形继承时,孙类的属性不确定继承哪个子类,通过虚继承可以使孙类属性只保留一份数据。虚继承的原理和vbptr有关(virtual base pointer 虚基类指针,指向vbtable(虚基类表)),感兴趣的自行百度~

cpp 复制代码
#include<iostream>
using namespace std;

class Animal{
public:
    int age;
};

class Sheep: virtual public Animal{  //继承前加virtual关键字,代表虚继承;此时公共的父类Animal类称为虚基类

};

class Tuo: virtual public Animal{

};

class SheepTuo: public Sheep, public Tuo{

};

int main(){
    SheepTuo st;
    st.Sheep::age = 120;
    st.Tuo::age = 25;  //虚继承将变量只存一份
    cout << st.Sheep::age << endl;  # 25
    cout << st.Tuo::age << endl;  # 25
    cout << st.age << endl;  # 25
}

虚函数和虚析构都是多态中的概念,而多态使用要求父类指针或引用指向子类对象,然后用该指针或引用调用函数,如果父类中成员函数不定义为虚函数,则地址早绑定,编译阶段确定函数地址,运行时就只会调用父类的方法,而不是子类的方式。如果想要动态调用子类方式,则需定义为虚函数,在运行阶段绑定函数地址,哪个子类调用就调用对应子类的方法。

cpp 复制代码
#include<iostream>
using namespace std;

class Animal{
    public:
    //不加入virtual,地址早绑定,编译阶段确定函数地址。调用func时就一直是Animal的地址
    //加入virtual,在运行阶段绑定函数地址,谁调用就执行谁。
    virtual void func(){  //称为 虚函数
        cout << "动物方法" << endl;
    }
};

class Cat: public Animal{
    public:
    void func(){
        cout << "小猫方法" << endl;
    }
};

class Dog: public Animal{
    public:
    void func() override{
        cout << "小狗方法" << endl;
    }
};

void dofunc(Animal *a){
    a->func();
}


int main(){
    Cat cat;
    dofunc(&cat);  # 小猫方法
    cout << sizeof(Animal) << endl;     //加入vitual,长度是8;  不加vitual,长度是1。本质是个虚函数(表)指针
}  

使用多态时,父类中的成员函数一般无实际意义,可以改为virtual void func() = 0,这种就是纯虚函数,有纯虚函数的类称为抽象类。

再看一段代码:

cpp 复制代码
#include<iostream>
using namespace std;

class Calculator{
public:
    int a, b;
    virtual int getResult() = 0; 
    Calculator(){
        cout << "父类的构造函数" << endl;
    }
    ~Calculator(){
        cout << "父类的析构函数" << endl;
    }

};


class AddCalculator: public Calculator{
public:
    int * age;
    int getResult(){
       return a + b;
    }
    AddCalculator(int age){
        this->age = new int(age);
        cout << "子类的构造函数" << endl;
    }
    ~AddCalculator(){
        cout << "子类的析构函数" << endl;
        if(age!=nullptr){
            delete age;
            age = nullptr;
        }
    }
};

int main(){

    Calculator *c ;
    c = new AddCalculator(90);
    delete c;
}
父类的构造函数
子类的构造函数
父类的析构函数

如上代码所示,是没有调用子类的析构函数,那age内存空间就不能被正确释放。如果想释放age内存空间,则父类的析构函数前一定要加上virtual关键字,就是虚析构函数。

当子类中有属性开辟到堆区时,需要使用虚析构,否则析构父类时,不会调用子类析构函数,造成内存泄漏。如果写为virtual ~Calculator() = 0,则称为纯虚析构,与纯虚函数不同的是纯虚析构,也要有代码实现,如下所示:

cpp 复制代码
class Calculator{
public:
    int a, b;
    virtual int getResult() = 0; 
    Calculator(){
        cout << "父类的构造函数" << endl;
    }
  
    virtual ~Calculator() = 0;  //纯虚析构,需要具体实现。
};

Calculator:: ~Calculator(){
    cout << "父类的析构函数" << endl;
}

有纯虚函数或纯虚析构的类称为抽象类,注意抽象类都不能实例化对象

13. vector容器

STL标准模板库,包括容器、算法、迭代器、仿函数等,其中vector是比较常用的容器。

vector 动态数组,单端数组。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int var;
vector<int> a;
# 插入容器数据
while(cin>var){
    a.push_back(var)
}
# 读取容器数据
for(vector<int>::iterator iv=a.begin();iv!=a.end();iv++){
    cout << *iv << " ";
}
# 或者直接使用算法 print方法指定格式输出
for_each(a.begin();a.end();print);
# 可以直接赋值
vector<int> v2;
v2 = a;
# 删除最后一个数据
v2.pop_back();
# 在头部位置插入数据,插入2个100
v2.insert(v2.begin(), 2, 100);
# 删除数据
v2.erase(--v2.end()); # 删除最后一个数据
v2.erase(v2.begin(), ++v2.begin()); # 删除开头2个数据,传入的是一个指针区间
# 清除数据
v2.clear();
# 访问数据
a[7]  a.front()  a.back()   # 下标访问、第一个元素、最后一个元素
# 容量和大小
a.capacity()  # 一般比元素个数要大,指的是开辟的容量大小
a.size()  # 元素个数
a.resize(3)  # 重新指定元素个数,少了就会删除,多了就按默认值填充,int类型填充0;但是这种方式capacity不会改变
# 如果想要capacity和size一样大,那需要收缩空间,可以通过swap函数操作
vector<int>(a).swap(a)
# swap本身是交换函数,用于交换两个vector数组的内容,但地址不会交换
v1.swap(v2);
# reserve(int len) 容器预留len个元素长度,预留位置不初始化,元素不可访问。主要用于减少vector在动态扩展容量时的扩展次数。
vector<int> v1;
v1.reserve(100);
14. 标准算法

以下都是标准算法,注意不支持随机访问迭代器的容器,不能用标准算法,如list容器。

vector和deque都可以使用标准算法。

cpp 复制代码
#include<algorithm>
sort(v.begin(), v.end())  # 指定区间 默认从小到大排序
reverse(v.begin(), v.end())   # 指定区间 倒序
replace(v.begin()++, --v.end(), 20, 200)  # 指定区间内将20替换为200

vector<int> iterator::it = find(v.begin(), v.end(), 10)
if(it != v.end()){cout << "找到了";}   # 从vector中查找元素10

int num = count(v.begin(), v.end(), 10)  # 计算区间内元素10的个数有几个
15. 随机数实现
cpp 复制代码
#include <iostream>
using namespace std;
#include <ctime>


int main(){
    //添加随机数种子,作用:利用当前系统时间生成随机数,防止每次随机数都一样
    srand((unsigned int)time(NULL));  //固定写法,记住就行。
    //生成随机数
    int num = rand()%100 + 1;   // 0~99,加1后是1~100;
相关推荐
人才程序员37 分钟前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
w(゚Д゚)w吓洗宝宝了39 分钟前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
AI人H哥会Java1 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构
开心工作室_kaic1 小时前
springboot461学生成绩分析和弱项辅助系统设计(论文+源码)_kaic
开发语言·数据库·vue.js·php·apache
觉醒的程序猿2 小时前
vue2设置拖拽选中时间区域
开发语言·前端·javascript
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 12课题、本地数据存储
开发语言·青少年编程·本地存储·编程与数学·goweb
唐墨1232 小时前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang
小老鼠不吃猫2 小时前
C++点云大文件读取
开发语言·c++
姚先生973 小时前
LeetCode 35. 搜索插入位置 (C++实现)
c++·算法·leetcode
Theodore_10223 小时前
3 需求分析
java·开发语言·算法·java-ee·软件工程·需求分析·需求