从C到C++

本文深入探讨C++的关键特性,包括字符串操作、迭代器使用、标准模板库(STL)应用、文件处理技巧以及异常处理等内容,并提供了一系列实用代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

杂记

  1. 数据类型string

    c里面只有字符数组,c++提供了字符串类型。

  2. 布尔类型bool

  3. if的条件判断里面,和C一样,非0还是表示true。
  4. c++里面for的i可以在里面声明,而c不行。

    //c++
    for(int i=0;i<4;i++)
       cout << i <<endl;
    //c
    int i;       
    for(i=0;i<4;i++)
       printf("%d ",i);    
  5. c++中允许函数重载,而c中会报错。

  6. c库导入时,include里面的time.h以及stdlib.h在c++中变成ctime以及cstdlib
  7. 全局变量也可以在函数内部被访问,即使我们在函数内部已经有了同样的变量声明,而c不允许同名。(双冒号表示引用外部变量)

    
    #include <iostream>
    
    using namespace std;
    
    double a = 123;
    int main()
    {
        double a = 12;
        cout << "Local a:  " << a   << endl;
        cout << "Global a: " << ::a << endl;
    }
    
  8. 我们可以声明命名空间,如果想使用命名空间里面的变量用::
  9. 引用类型

    int a = 12;
    int &b = a;
    • &在此不是求地址运算,而是起标识作用。
    • 类型标识符是指目标变量的类型。
    • 声明引用时,必须同时对其进行初始化。
    • 引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。 b=1; 等价于 a=1;
    • 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&b与&a相等。
  10. 引用类型作为函数参数传递

    
    #include <iostream>
    
    using namespace std;
    //引用传递
    void swap1(int &a, int &b)
    {
        int p = a;
        a = b;
        b = p;
    }
    //指针传递
    void swap2(int *a, int *b)
    {
        int p = *a;
        *a = *b;
        *b = p;
    }
    int main()
    {
        int a = 12;
        int b = 11;
        //swap1(a,b);
        swap2(&a,&b);
        cout << "a:  " << a << endl;
        cout << "b:  " << b << endl;
    }

    • 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
    • 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元(指针也是),形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好,但坏处是函数中可能会修改内存中该变量的值,若要求其不能被修改,则可以指定为const。
    • 深层次上来说,第一点里说的效果一样,其实不尽相同,这就牵扯到值传递和引用传递的区别了。

    值传递和引用传递

    • 值传递 (pass by value),指针传递(pass by pointer),当发生函数调用时,需要给形参分配存储单元、当传递是对象时,要调用拷贝构造函数。而且指针最后析构时,要处理内存释放问题。
    • 引用传递(pass by refenrence),在内存中没有产生形参。效率大大提高!也不用处理指针的析构问题。
  11. new运算符以及delete运算符

    
    #include <iostream>
    
    using namespace std;
    
    int main()
    {
        int a = 12;
        int *p = new int;//p指向该内存
        *p = a;
        cout<<a<<endl;
        cout<<*p<<endl;
    
        delete p;//释放new的内存
        p = NULL;//重置指针p
        cout<<a<<endl;
        cout<<*p<<endl;//程序奔溃,访问空指针
    }
    
  12. 结构使用上基本一致,但c++可以把声明后的结构直接当类型来用,而c必须加上struct。
    c:

    struct point{
            int x;
            int y;
    };
    struct point* getNewPoint()
    {
        struct point *p = malloc(sizeof(struct point));
        p->x = 0;
        p->y = 0;
        return p;
    };
    main()
    {
        struct point p1 = {1,2};
        struct point *pp = getNewPoint();
        printf("p1:x=%d,y=%d\n",p1.x,p1.y);
        printf("*pp:x=%d,y=%d\n",pp->x,pp->y);
    }

    c++:

    
    #include <iostream>
    
    using namespace std;
    
    struct point{
            int x;
            int y;
    };
    struct point* getNewPoint()
    {
        struct point *p = new struct point;//可以不加struct
        p->x = 0;
        p->y = 0;
        return p;
    };
    main()
    {
        struct point p1 = {1,2};//可以不加struct
        struct point *pp = getNewPoint();//可以不加struct
        cout<<p1.x<<" "<<p1.y<<endl;
        cout<<pp->x<<" "<<pp->y<<endl;
    }
    
  13. const的修饰问题:
    如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向的为常量;如果const位于星号的右侧,则const修饰的是指针本身,即指针本身是常量
  14. 非常量作为常量函数的参数会编译出错;反之可以。这意味着写函数时,形参尽可能加上const,好让调用时无论是常量还是非常量都编译通过。
  15. map引用作为形参时,如果指定了形参为const,则如果在函数中要用到迭代器,则必须使用const_iterator,因为一般的iterator可以修改map。

    
    #include <iostream>
    
    
    #include <map>
    
    using namespace std;
    
    void display(const map<int,int>& mp)
    {
        for(map<int,int>::const_iterator itr = mp.begin(), itr_end = mp.end(); itr!=itr_end; itr++)
            cout<<"key: "<<(char)itr->first<<"  value: "<<itr->second<<endl;
    }
    main()
    {
        map<int,int> mp;
        mp['1'] = 1;
        mp['2'] = 2;
        mp['3'] = 3;
        display(mp);
    }
  16. 结构体内无方法(c语言)–>结构体内增加方法声明及定义(c++)–>结构体内声明方法,外面进行定义,用::声明方法属于哪个结构体(c++)。
  17. class和struct区别:
    概念:class功能多于struct,但class对象位于堆效率低于位于栈的struct。
    默认控制力度:struct作为数据结构的实现体,它默认的数据访问控制是public(实例可以直接访问内部数据)的,而class作为对象的实现体,它默认的成员变量访问控制是private(实例内部数据或方法只能被内部的方法访问,不能直接通过实例访问)的。
    类型:struct是值类型,class是引用类型,因此它们具有所有值类型和引用类型之间的差异。
    效率:由于堆栈的执行效率要比堆的执行效率高,但是堆栈资源却很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理作为基类型对待的小对象,而class来处理某个商业逻辑。
    关系:struct不仅能继承也能被继承 ,而且可以实现接口,不过Class可以完全扩展。内部结构有区别,struct只能添加带参的构造函数,不能使用abstract和protected等修饰符,不能初始化实例字段。
  18. 如果某类的构造函数不接受参数,声明对象时不能使用括号(和java不同!),如直接Point board而不是Point board(),用new来分配内存时,也是这样,如果构造函数有参数,则可以Point board(5)
  19. 新特性——继承。父类方法声明为virtual(虚函数),子类可以重写该函数。此外,纯虚函数(如父类中virtual void draw() = 0)必须被重写(如子类中virtual void draw(){...}
  20. new 时加括号与不加括号区别:
    • 加括号调用没有参数的构造函数,不加括号调用默认构造函数或唯一的构造函数,看需求
    • C++在new时的初始化的规律可能为:对于有构造函数的类,不论有没有括号,都用构造函数进行初始化;如果没有构造函数,则不加括号的new只分配内存空间,不进行内存的初始化,而加了括号的new会在分配内存的同时初始化为0。
  21. 任何时候想要用一个继承了父类接口的类,你都需要使用指针来传递它。(?)
  22. class后面:表示继承自;函数后面:表示初始化参数(值)或函数(实参)。
  23. 通过在父类中将析构函数标志为虚的,在delete释放一个父类接口时,如果传入的是子类,那么子类重写后的析构函数会被调用,子类可以被毁灭。(便于编写一个函数统一销毁所有子类,具体销毁方法可以在子类中定义)
  24. 增加了static,静态变量、方法。
  25. using namespace std作用是使用cincout等对象时不用写成std::cinstd::out
  26. 异常。
  27. stringchar *转换:

    char *p = "c-string";
    string str(p);
    cout << str;
    
    const char *s = str.c_str();
    printf("%s",s);
  28. auto类型说明符,根据初始值自动确定类型。
  29. decltype类型指示符,根据里面的类型确定变量类型,可以不初始化,除了里面是解引用操作、是变量名加括号的表达式以及本来就是引用类型时。

    char *p;
    char &q = *p;
    
    decltype(*p) c;
    decltype((p)) e;
    decltype(q) d;

    以上三种声明都是引用类型。


    三五法则

  30. 需要析构函数的类也需要拷贝和赋值操作。若一个类需要析构函数,则代表其合成的析构函数不足以释放类所拥有的资源,其中最典型的就是指针成员(析构时需要手动去释放指针指向的内存)。

    T f(T p)
        {
            T tmp = p;//如果没有定义赋值操作,则指向同一块内存
            //...
            return tmp;//tmp析构函数销毁,同时p也销毁了
        }
  31. 需要拷贝操作和类也需要赋值操作,反之亦然。
  32. 析构函数不能是删除的。(?)
  33. 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的。
    如果没有这条规则,可能会创造出无法被删除的对象。 理论上来说,当析构函数不能被访问时,任何静态定义的对象都不能通过编译器的编译,所以这种情况只会出现在与动态分配有关的拷贝/默认构造函数身上。
  34. 如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作。

    指针与下标

  35. 内置下标运算符所用的索引值不是无符号类型,而vector和string的下标运算符是无符号类型。——C++primer第五版108。

    
    #include <iostream>
    
    
    #include <string>
    
    using namespace std;
    int main()
    {
        int arr[] = {0,1,2,3};
        int *p = &arr[2];
    
        printf("%d\n",p[1]);//3
        printf("%d\n",*(p+1));//与上面等价
    
        printf("%d\n",p[-1]);//1
        printf("%d\n",*(p-1));//与上面等价
    
        string str = "string";
        char *pc = &str[3];
    
        printf("%c\n",pc[1]);//n
        printf("%c\n",*(pc+1));//与上面等价
    
        printf("%c\n",pc[-1]);//r
        printf("%c\n",*(pc-1));//与上面等价
    
        printf("%c\n",str[-1]);//错误用法,结果是未知的
    }
    
    

IO

这里写图片描述

cin cout(iostream)

cin可以连续从键盘读取想要的数据,以空格、tab或换行作为分隔符;
如读入文件的时候可以使用while(cin>>str){...},当cin遇到文件结束符(windows中为:ctrl +Z , Unix 中为:ctrl +D),或无效输入才能使cin状态无效,换行不能使之退出循环,只会一直等待

cin.clear()//将状态码重设为有效值
cin.sync()//清空输入流(cout.sync()为写出输出流)

文件操作(fstream)

//打开文件
ifstream ifs;
ifs.open("tmp.txt");

//操作文件
string str;
ifs >> str;

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

string流(sstream)

sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样。

istringstream

将string作为IO源,进行数据读取。一般用于对一行文本处理的情况,如输入为人名-电话(不固定个数):

mrogan 2011012 2000212
drew 90912 1212123
lee 12312123
#include "stdafx.h"
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

struct PersonInfo{
    string name;
    vector<string> phones;
};

int main()
{   
    string line, word;
    vector<PersonInfo> people;
    while (getline(cin, line)){//这里假设是从文件中读取的,最后会有EOF信号,否则需要手动ctrl+z进行输入终止
        PersonInfo info;
        istringstream record(line);//将line作为io源
        record >> info.name;
        while (record >> word)//当record中数据读取完时,会触发一个EOF信号,终止循环
            info.phones.push_back(word);
        people.push_back(info);
    }
    //输出保存的结果
    for (vector<PersonInfo>::iterator itr = people.begin(); itr != people.end(); itr++){
        cout << itr->name << " has";
        for (int i = 0; i < itr->phones.size(); i++)
            cout << " " << itr->phones[i];
        cout << endl;

    //一种更简单的遍历方式
    for (const auto &entry:people){
        cout << entry.name << " has";
        for (int i = 0; i < entry.phones.size(); i++)
            cout << " " << entry.phones[i];
        cout << endl;
    }

    system("pause");
    return 0;
}
ostringstream

方便格式化输出到string中。

os.str('');//清空缓冲区
string str = os.str();//获得缓冲区的字符串
os.

例:汉诺塔问题,先输出总数,然后后100个解(如果有的话)
需要先把结果用vector<string>保存起来,这里string是格式化过的,如1:A->C

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;

vector<string> vec;
int time = 1;
void hanoi(char from, char mid, char to,int num)
{
    if (num == 0)return;
    hanoi(from, to, mid, num - 1);
    ostringstream os;
    os << time++<<":"<<from<<"->"<<to;
    vec.push_back(os.str());
    hanoi(mid, from, to, num -1);
}

int main() {
    int n;
    cin >> n;
    hanoi('A','B','C',n);
    cout << time << endl;
    if (time<=100)
        for (string i : vec)
            cout << i << endl;
    else
        for (int i = vec.size() - 100; i <= vec.size() - 1; i++)
            cout << vec[i] << endl;
    system("pause");
    return 0;
}

实用标准模板库(STL)

string库

string s;
cin>>s;//从cin中读取一个字符串,以空格分隔
getline(cin,s);//从cin中读取一行,以'\n'分隔
搜索

返回值是unsigned,类型为string::size_type,不用int等带符号类型来保存返回结果,用auto。如果没找到,则值为string::npos,它是一个const string::size_type类型,初始化为-1。(所以,)

string str = "hello1234";
auto pos1 = str.find("ly");//没找到,pos1 == string::npos;
auto pos2 = str.find("ll");//找到,pos2 == 2 

string nums = "0123456789";
auto pos3 = str.find_first_of(nums);//找str中第一个在nums中出现的字符下标
getline函数

从输入源读取某个字符前的字符,会把分隔符也读进来,但是不保存在目标变量中,而是丢掉。如从标准输入读一行:getline(cin, str, '\n');str中和cin中都没有’\n’。

数值<->string
string->int

int stoi(const string &str, size_t* idx = 0, int base = 10)
任意进制数转10进制数,返回一个int数

  • str: 要转换的string
  • idx: 为size_t*类型,是从str中解析出一个整数后的下一个字符的位置(如果还要继续转换,则自己弄一个指针存一下地址,否则直接用nullptr就行)
  • base: 指出string中要转换的数的进制,即str所代表的是个什么进制的数,如是base默认为10, 若base = 0, 表示由编译器自动判定str所代表的数的进制
int->string

string::to_string()

vector库

存大小可变的数组,方括号只能访问已经分配的内存。
方法:

  1. push_back 在数组的最后添加一个数据
  2. pop_back 去掉数组的最后一个数据
  3. at 得到编号位置的数据
  4. begin 得到数组头的指针
  5. end 得到数组的最后一个单元+1的指针
  6. front 得到数组头的引用
  7. back 得到数组的最后一个单元的引用
  8. max_size 得到vector最大可以是多大
  9. capacity 当前vector分配的大小
  10. size 当前使用数据的大小
  11. resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
  12. reserve 改变当前vecotr所分配空间的大小
  13. erase 删除指针指向的数据项
  14. clear 清空当前的vector
  15. rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)
  16. rend 将vector反转构的结束指针返回(其实就是原来的begin-1)
  17. empty 判断vector是否为空
  18. swap 与另一个vector交换数据

其中,size是实际存的元素个数,而capacity会自动调整大小,大小为 2n 2 n

map库

访问可以用中括号(和数组一样,只不过一个方括号内是索引,一个是key)

迭代器 iterator

迭代器是一个指向元素位置的指针。

  • vector迭代器:
#include <iostream>
#include <vector>
using namespace std;

main()
{
    vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    for(vector<int>::iterator itr = vec.begin(), itr_end = vec.end(); itr!=itr_end; itr++)
        cout<<*itr<<endl;
    //这样遍历更简单
    for (auto &entry : vec)
        cout << entry << endl;
}

如果目的是遍历的话,更方便的是用auto。

对于map来将,迭代器指向key-value结构,可以用->访问键和值。

#include <iostream>
#include <map>
using namespace std;

main()
{
    map<int,int> mp;
    mp['1'] = 1;
    mp['2'] = 2;
    mp['3'] = 3;
    for(map<int,int>::iterator itr = mp.begin(), itr_end = mp.end(); itr!=itr_end; itr++)
        cout<<"key: "<<(char)itr->first<<"  value: "<<itr->second<<endl;
}
查找不成功

查找某个key是否存在时,可以有下面两种写法:

if(m.count(key)>0)
{..}

if(m.find(key)!=m.end())
{..}

queue

#include <iostream>
#include <queue>
using namespace std;

main()
{
    queue<int> myque;
    int myint;
    cout << "Please enter some integers (enter 0 to end):\n";

    do{
        cin >> myint;
        myque.push(myint);
    }while(myint);

    cout << "myqueue contains: ";
    while(!myque.empty()){
        cout<<' '<<myque.front();
        myque.pop();//注意:pop的返回值为void,用front()看值
    }
    cout << endl;
    return 0;
}

priority_queue

priority_queue<int> q;//默认降序排列,即大值优先级高

//1. 引入<funcional>库实现升序
priority_queue<int, vector<int>, less<int>> q;//降序排列
priority_queue<int, vector<int>, greater<int>> q;//升序排列

//2. 自定义cmp结构体时,return xx<xx; 表示降序
struct cmp{
    bool operator()(Node* &a, Node* &b)
    {
        return a->value > b->value;
    }
};
priority_queue<Node*,vector<Node*>,cmp> que;

//3. 目标类中直接定义比较方式
class student{
    ...
    friend bool operator < (const student &s1, const student &s2){
       return s1.grade < s2.grade;//降序
    }   
}
priority_queue<student> que;

stack

#include <iostream>
#include <stack>
using namespace std;

main()
{
    stack<int> mystack;
    for(int i=0; i<5; ++i)
        mystack.push(i);
    cout << "Poping out elements...";
    while (!mystack.empty()){
        cout << ' ' << mystack.top();
        mystack.pop();//注意:pop的返回值为void,用top()看值
    }
    return 0;
}

格式化输出库 iomanip

    cout << setw(10) <<"hello"<<" world"<<endl;//默认左边补空格,右对齐
    cout << setw(10) <<left<<"hello"<<" world"<<endl;//右边补空格,左对齐
    cout << setfill('-') << setw(10)<< left<<"hello"<<" world"<<endl;//改变填充字符

    cout.fill('-');//全局改变填充字符,下面不用加setfill了
    cout << setw(10)<< left<<"hello"<<" world"<<endl;

    cout.setf( ios_base::left );//全局设置左对齐
    cout << setw(10) <<"hello"<<" world"<<endl;

    cout << setprecision(3) << 3.145 <<endl; //3.15,设置数值总长度(四舍五入)
    cout << 3.145 <<endl; //3.15,直到下一次调用setprecision前都有效(而setw只有效一次)

算法库

sort

默认升序,快排,不稳定,若要降序,用<functional>库的greater结构实例。

int main () {
    int foo[]={10,20,5,15,25};
     int bar[]={15,10,20};
     std::sort (foo, foo+5, std::greater<int>());  // 25 20 15 10 5
     std::sort (bar, bar+3, std::greater<int>());  // 20 15 10 
     if (std::includes (foo, foo+5, bar, bar+3, std::less<int>()))
       std::cout << "foo includes bar.\n";
     return 0;
}

其中,include可以测试一个序列是否在一个序列中。

c++的格式化输出

如果想要保留2位小数,用cout如何实现?

cout.precision(3);//设置精度为3
cout << fixed;//设置一下,不然是包括整数总精度为3
cour <<54.3121;//54.312
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值