小细节总结(持续更新-----)


前言

​ 对于自己学习C++编程语言时遇到的一些细节问题做了总结

int pos = xx.find(“,” , 0) ;

find后面要加个默认的查找位置 0

开辟新空间相关( new 数据类型)

开辟元素

​ 数据类型* a = new 数据类型(10);

开辟一级指针数组

数据类型* arr = new 数据类型[10];

父类 * sb = new 子类; (满足多态的条件下) // 父类指针指向子类地址

开辟二级指针数组

数据类型 **arr = new 数据类型* [size]; // 二级指针指向一级指针的地址

常用添加及存储数据的方式(增)

当数据类型都一样的时候

创建一维数组即可

静态的一维数组 静态存储区域分配 编译的时候确定长度

1. ` 数据类型  数组名[ 数组长度 ]; `
2. `数据类型  数组名[ 数组长度 ] = { 值1,值2 ...};`
3. `数据类型  数组名[ ] = { 值1,值2 ...};`

动态的一维数组 存放在堆区 程序运行的时候确定长度

数据类型* arr = new 数据类型[10];

需要New的时候一般对于复杂类型,这时候内存分配是在堆区。比如自定义的类类型,或者需要大量内存空间的时候使用

当数据类型不一样或自定义类的时候

​ 可以用 Vector / Deque / Map(Multimap) 容器 。

#include <vector>
vector<数据类型> v;

//  遍历容器里面的元素
for(vector<数据类型>::iterator/*也可用auto关键字*/ it = v.begin(); it != v.end(); it++) //传统for循环
{
    cout << *it << end;
   
}
for(i : v)    //范围for循环
{
    cout << i << endl;
}

或者满足多态条件下的类可以用二级指针:

父类** father = new 父类 * [];      //开辟父类指针数组空间
父类* sb = new 子类;               //父类指针指向子类地址
father[i] = sb;                  // 将 子类地址 存放在 父类数组中

文件读写交互

一般来说步骤有 写入文件 、读取文件、输出文件

写入文件

void SpeechManager::saveRecord()
{
    ofstream ofs;
    ofs.open("speech.csv", ios::out | ios::app);     //用追加的方式打开文件  -- 写文件
//将每个人数据写入到文件中
for (······)
{
    ofs << ····· << ","
        
}
ofs << endl;

//关闭文件
ofs.close();

cout << "记录已经保存" << endl;
}

读取文件内容 (一般放入管理类的构造函数里)

  1. 第一次使用,文件未创建
  2. 文件存在,但是数据被用户清空
  3. 文件存在,并且保存职工的所有数据
void SpeechManager::loadRecord()
{
    

     ifstream ifs("speech.csv", ios::in); //输入流对象 读取文件
    
    //1、文件不存在的情况
    if (!ifs.is_open())
    {
        this->fileIsEmpty = true;
        cout << "文件不存在!" << endl;
        ifs.close();
        return;
    }
    

    //2、文件为空的情况
    char ch;
    ifs >> ch;
    if (ifs.eof())
    {
        cout << "文件为空!" << endl;
        this->fileIsEmpty = true;
        ifs.close();
        return;
    }

    //文件不为空
    this->fileIsEmpty = false;

    ifs.putback(ch); //读取的单个字符放回去

    string data;
    int index = 0;
    while (ifs >> data)
    {
        //cout << data << endl;
        vector<string>v;

        int pos = -1;
        int start = 0;

        while (true)
        {
            pos = data.find(",", start); //从0开始查找 ','
            if (pos == -1)
            {
                break; //找不到break返回
            }
            string tmp = data.substr(start, pos - start); //找到了,进行分割 参数1 起始位置,参数2 截取长度
            v.push_back(tmp);
            start = pos + 1;
        }

        this->m_Record.insert(make_pair(index, v));
        index++;
    }
    ifs.close();
}

查看文件内容

  • 在speechManager.cpp中实现成员函数 void showRecord();
void SpeechManager::showRecord()
{
    
    if(this->fileIsEmpty)
    {
        cout << "文件不存在,或记录为空!"<<endl;
    }
    else
    {   ·······  }

system("pause");
system("cls");
}

常用删除数据的方式(删)

删除数组中的一个或一组元素

​ 指定删除元素位置之后的元素都往前移。

// index 为指定删除元素 遍历到最后一个元素的-1的
// 让数组前面一个元素赋值为后面一个元素的值 实现元素前移

for (int i = index; i < this->m_EmpNum - 1; i++) 
        {
            this->m_EmpArray[i] = this->m_EmpArray[i + 1];
        }

模板相关

首先需要知道的是把模版类的定义和实现分成.h与.cpp写,编译将会出错。

容器遍历几种方式

vector<int> a = { 0,1,2,3,4,5,6,7,8,9 };

for (vector<int>::iterator it = a.begin(); it != a.end(); ++it)
{
    cout <<*it << " ";
}
cout << endl;

// 用关键字 auto 可自动识别 it的数据类型
for (auto it = a.begin(); it != a.end(); ++it)
{    
    cout << *it << " ";
}

cout << endl;

//知道容器数量,可用循环下标遍历
for (int i = 0; i < 10; ++i)
{
    cout << a[i] << " ";
}
cout << endl;

//可用范围遍历输出数据,但范围for不能遍历容器的增加和删除相关操作。
for (auto r : a)
{
    cout << r << " ";
}

char[]取地址

C++ 中,将 char *char[] 传递给 cout 进行输出,结果会是整个字符串,如果想要获得字符串的地址(第一个字符的内存地址),可使用以下方法:

强制转化为其他指针(非 char*)。可以是 void *,int *,float *, double * 等。* 使用 &s[0] 不能输出 **s[0]**(首字符)的地址。因为 &s[0] 将返回 char*,对于 char*char 指针),cout 会将其作为字符串来处理,向下查找字符并输出直到字符结束 *****。

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   char  var[MAX] = {'a', 'b', 'c'};
   char  *ptr;
 
   // 指针中的数组地址
   ptr = var;
            
   for (int i = 0; i < MAX; i++)
   {

      cout << "Address of var[" << i << "] = ";
      cout << (int *)ptr << endl;
 
      cout << "Value of var[" << i << "] = ";
      cout << *ptr << endl;
 
      // 移动到下一个位置
      ptr++;
   }
   return 0;
}

输出结果:

Address of var[0] = 0x7fffd63b79f9
Value of var[0] = a
Address of var[1] = 0x7fffd63b79fa
Value of var[1] = b
Address of var[2] = 0x7fffd63b79fb
Value of var[2] = c

C++常见的字符串处理函数

#include< string >

  1. 应用于查找的find()函数
  2. 子串substr()函数
  3. 替换replace()函数
  4. 插入:insert()函数
  5. 添加字符串:append()函数
  6. 交换字符串:swap()函数
  7. 字符串比较函数:compare()
  8. 字符串大小
    #include< string.h >
    strcpy(s1,s2)
    strcat(s1,s2)
    strlen(s1)
    strcmp(s1,s2)
    strchr(s1,ch)
    strstr(s1,s2)
    memcpy (void *dest, const void *src, int size)

内存对齐

什么是字节

​ 字节(Byte)是存储数据的基本单位,并且是硬件所能访问的最小单位(解释:硬件是通过地址总线访问内存的,而地址是以字节为单位进行分配的,所以地址总线只能精确到字节)
​ 内存中存储数据的最小单位是“位(Bit)”。
​ 即字节是存储数据的基本单位,位是存储数据的最小单位。
​ 内存里面存放的全是二进制代码,位只能是 0 或者 1, 8位 = 1字节,换算关系如下:
​ 1B=8bit
​ 1KB=1024B
​ 1MB=1024KB
​ 1GB=1024MB

64位编译器 32位编译器
char :1个字节 char :1个字节
char*(即指针变量): 8个字节(64位的寻址空间是2^64, 即64个bit,也就是8个字节。同理32位编译器) char*(即指针变量): 4个字节
short int : 2个字节 short int : 2个字节
int: 4个字节 int: 4个字节
unsigned int : 4个字节 unsigned int : 4个字节
float: 4个字节 float: 4个字节
double: 8个字节 double: 8个字节
long: 8个字节 long: 4个字节
long long: 8个字节 long long: 8个字节
unsigned long: 8个字节 unsigned long: 4个字节

问题引出:

struct A{
    char a;
    int b;
    short c;
};//sizeof(A) 为12字节

struct B{
    char a;
    short b;
    int c;
}; //sizeof(B) 为8字节

上面两个结构体拥有相同的数据成员 char、short 和 int,但由于各个成员按照它们被声明的顺序在内存中顺序存储,所以不同的声明顺序导致了结构体所占空间的不同。

什么是内存对齐

​ 内存对齐:编译器将程序中的每个“数据单元”安排在字的整数倍的地址指向的内存之中

内存对齐的原则

  • 1、对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。
  • 2、在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

进行内存对齐的原因

(1) 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。(访问两次内存,一次获取 “前一部分” 的值,一次获取 “后一部分” 的值.)

(2) 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

(3) 空间原因:没有进行内存对齐的结构体或类会浪费一定的空间,当创建对象越多时,消耗的空间越多。

内存对齐的优点

​ 便于在不同的平台之间进行移植,因为有些硬件平台不能够支持任意地址的数据访问,只能在某些地址处取某些特定的数据,否则会抛出异常;
​ 提高内存的访问效率,因为 CPU 在读取内存时,是一块一块的读取。

智能指针

cat.h

#pragma once
#include <string>
#include <iostream>
using namespace std;


class cat
{
public:
    //构造函数
    cat(string name1):name(name1)
    {
        cout << "构造函数:" << name << endl;
    }
    cat() = default;
    //析构函数
    ~cat()
    {
        cout << "退出构造:" << name << endl;
    }
    
    void get_name() const
    {
        cout<< name << endl;
    }
     
    void set_name(const string &tmp)
    {
        this->name = tmp;
    }

private:
    string name;
};
int main()
{
    //stack调用 栈上创建
    cat A("xx");
    //结果: 
    //构造函数xx
    //退出构造xx

     //heap
  cat* me = new cat("金毛犬");
  {
      cat* me1 = new cat("萨摩耶");
      delete me1;
  }
  delete me;
    //结果: 只有手动加入delete关键字后才能完成析构函数
    //构造函数金毛犬
    //构造函数萨摩耶
    //退出构造萨摩耶
    //退出构造金毛犬

    //smart point 用独占或者共享指针的方式创建
    //1.方式1将 raw pointer 赋予 unique_ptr 
    cat* me = new cat("柯基");
    unique_ptr<cat> tmp{ me };
    //2. 方式2
    unique_ptr<cat> tmp(new cat("柯基");
     //3. 方式3 推荐
    unique_ptr<cat> tmp = make_unique<cat>("柯基");
    shared_ptr<cat> tmp = make_shared<cat>("柯基");
                        
    auto tmp = make_unique<cat>("柯基");
     auto tmp = make_shared<cat>("柯基");

    system("pause");
    return 0;
}

如示例所示:

安全的创建智能指针:

完整:
unique_ptr tmp = make_unique(“柯基”);
shared_ptr tmp = make_shared(“柯基”);

省略:
auto tmp = make_unique(“柯基”);
auto tmp = make_shared(“柯基”);

值传递不可以 Copy ,只能Move

//引用传递 pass by ref
void XX(const unique_ptr<cat> &c)
{

}
//值传递
void XX(unique_ptr<cat> c)
{
    
}

unique_ptr 通过move值传递后 指针消失。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏