0
点赞
收藏
分享

微信扫一扫

第1篇:对C/C++指针与字符串的总结

字符串是以ASCII字符并且以NUL(即'\0')结尾 表示的字符序列

C中的字符串机制

以字符串字面量定义字符串时会将其分配到字面量池中,这个内存区域通常保存组成字符串的字符序列,该内存区域通常被认为是全局/静态的。字符字面量在池中通常只有一份副本并且是只读的,这样可以减少程序的内存占用率。

首先,理解C的字符串运行机制,下面一段简单的代码可以得出关于字符串的不同结论

#include <stdio.h>

char *g="Hello";

int main(int argc, char const *argv[])
{
    char s[]="Hello";
    char *c="Hello";
    printf("字符指针c的内存地址:%p\n",&c);
    printf("数组s的内存地址:%p\n",&s);
    printf("字符指针g的内存地址:%p\n",&g);
    printf("字符指针c指向Hello的的内存地址: %p\n",&c[0]);
    printf("字符指针g指向Hello的的内存地址: %p\n",&g[0]);
    printf("变量s数组的Hello副本的内存地址:%p\n",&s[0]);
    printf("Hello字面量的尺寸 %lu\n",sizeof("Hello"));
    
    return 0;
}

从示例代码中,全局区声明并以字符串字面量初始化了字符指针g,g的内存地址是0x108993018,这个地址位于全局数据区内,而指针g它指向的“Hello”字面量地址是0x108992e9e.这个地址位于字面量池内. 同时,我们也从main函数内部定义了两个局部变量字符指针变量c,c指针指向的字面量的内存地址和全局字符指针指向的"Hello"字面量是一样的,那么这里可以得出以下关于声明char指针并以字面量初始化的时候,可以得出以下特性。

另外,在main函数内部的数组s,它内部持有字符串数组的地址和字面量池的字符串的地址是不一样的。换句话说,

以下是运行的随机结果:


C++中的字符串的机制

string是一个包含多个数据成员的字符串对象,这里只是补充《C++ Primer》这本书关于string对象内部机制没详细阐述,做个笔录,这里不会罗列所有的string对象的api,不熟悉的同学可以看《C++ Primer》相关内容。

  • c_str()是一个指针指向动态分配空间中的字符数组中第一个字符的地址。

  • size()包含字符串的长度。

  • capacity()包含当前可能存储在数组中的有效字符数(额外的NUL字符不计算在内)。

  • malloc内存分配
    一个涉及到malloc内存管理程序的实现需要3个字段,每个字段都是三个不同指针:

    • 指向已分配内存的指针;
    • 字符串的逻辑大小(该字符串末尾是NUL字符);
    • 分配的内存大小(必须大于或等于逻辑大小);


#include <iostream>
#include <cstdlib>
#include <string>

using std::string;
using std::cout;
using std::endl;

//重写string类的new操作符,添加一个可以识别malloc操作的输出
void* operator new(std::size_t n){
    cout<<"分配"<<n<<"字节"<<endl;
    return malloc(n);
}

void operator delete(void *p) throw(){
    free(p);
}

int main(int argc, char const *argv[])
{
    string s("HELLO"); //直接初始化
    cout<<"初始化时的状态:"<<endl;
    cout<<"sizeof:"<<sizeof(s)<<endl;
    cout<<"size:"<<s.size()<<endl;
    cout<<"分配的内存尺寸(capacity):"<<s.capacity()<<endl;

    for(size_t i=6;i<24;++i){
        s.push_back('+');
        cout<<i<<":"<<s<<endl;
    }

    cout<<"push_back('+')之后的内存尺寸是"<<endl;
    cout<<"sizeof:"<<sizeof(s)<<endl;
    cout<<"size:"<<s.size()<<endl;
    cout<<"分配的内存尺寸(capacity):"<<s.capacity()<<endl;

运行结果



在for循环前:我们通过调用string三个提到三个基本方法,起初分配的内存是24字节,但允许容纳有效的字符是22个,为什么呢?

上面的代码,for的循环的长度加大,总之大于24的任意一个正整数,多实验几次。会得到如下基本特征。

  • 当size()方法得出字符数达到capacity()得出的有效容纳的字符数,string对象内部就会触发malloc的内存重新扩容。
  • 每次malloc扩容后的申请的内存空间尺寸是之前的内存空间尺寸的2倍。

基于这两点,C++的string对象内部封装了涉及malloc操作的指针操作,这大大减轻了程序猿对指针操作不当,带来程序不可预测的可能性。同时使用双倍扩容的方法也最大限度减少了因字符串长度后续增加的频繁malloc操作带来的系统消耗。但是它是以内存空间为代价的,堆里和字面量池总有着相同一份相同的字符串副本。

后记:
理解C++的string对象底层其实就是malloc动态分配堆内存的机制之后,后面关于字符串的拼接,复制,查找等基本原理,你心里就有底了.要彻底理解字符串的话,推荐阅读《深入理解c指针》这本书,里面关于字符串的描述比《征服C指针》讲得更加深入。

额外问题:

调试C++程序的时候,有时你需要查看string对象内部的指针,虽然c_str()可以输出字符串首个字符的内存地址,但标准库cout操作会自动对c_str()的内部指针做解引操作,因此cout不能直接得出字符串的地址,而是打印对应的字符串。不需要折腾cout,直接简单的粗暴方法是用C的printf函数

#include <stdio.h>
#include <string>
int main(int argc, char const *argv[])
{
    string s("HELLO"); //直接初始化
    printf("变量的s内存地址:%p\n",&s);
    printf("变量的s2内存地址:%p\n",&s2);
    printf("字符串对象s的地址:%p\n",&s[0]);
    printf("字符串对象s2的地址:%p\n",s2.c_str());

    printf("字符串字面量:%p\n",&"HELLO");
    return 0;
}

输出

变量的s内存地址:0x7ffee7221568
变量的s2内存地址:0x7ffee7221550
字符串对象s的地址:0x7ffee7221569
字符串对象s2的地址:0x7ffee7221551
字符串字面量:0x1089dfea8
举报

相关推荐

0 条评论