C/C++ 学习笔记:字符串、数组相关

来源:互联网 发布:mysql分割字符 编辑:程序博客网 时间:2024/05/22 02:17

  • C/C++ 中所有字符串字面值都由编译器自动在末尾添加一个空字符,即默认以 \0 结尾

eg:字面值 "tang"  实际上是 "tang\0",字面值"tang\0" 实际上是"tang\0\0"

  • char 数组

char StrArray[4];// 如果数组 StrArray 不是全局变量,内容是随机的。char StrArray[4] = {'B', 'u'};// 这里会默认给剩余的 2 个元素补上 \0,若 4 个字符全指定了,就不会在最后加上 \0,因为超出数组范围了。char Str[] = {'t', 'a', 'n', 'g'};// 不会以 \0 结尾char Str1[] = "tang";// 因为字符串字面值是以 \0 结尾的,所以数组 Str1 也是以 \0 结尾char Str2[] = "tang\0";// 尽管字面值有一个 \0,编译器还是会默认给字符串字面值后面加一个 \0,即这个字符串常量实际上是 "tang\0\0",所以数组 Str2 实际会以这个字符串常量被默认添加的那个 \0 结尾。

所以:sizeof(Str为 4sizeof(Str1为 5sizeof(Str2为 6

  • char指针

不会自动在末尾补 \0

  • string 型变量
1. 以 \0 结尾(不会自动补  \0)

如果把上面不含 \0 的 Str 数组赋值给 string 型变量,则实际会是一直赋值到 \0 结束。相当于调用 string(constchar*s),然后 Str 退化为指针,然后赋值的时候会一直找到 \0 才结束。

2. 其 size()  和 length() 函数得到的都是不含 \0 的长度

重要的

  1. 用字符串初始化指针时,指针指向的是放在常量区的字符串常量;
  2. 而用字符串来初始化数组时,是用字符串常量的内容初始化数组的内容;
  3. 数组名是一个右值类型( 比如字符型数组的类型为  char*const ),不能作为左值来接收另一个数组。
数组名的本质

1. 数组名指代一种数据结构,这种数据结构就是数组;

2. 数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;   

char str[10];   

str++;// 编译出错,提示 str不是左值

3. 数组名作为函数形参时,沦为普通指针。

void Func( char str[100] ) {    cout << sizeof( str ) << endl;// 32位机器下,这里为 4}// Func( char str[100] ) 函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;// 在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
  • sizeof 与 strlen 的区别
1. sizeof(...) 是运算符,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。跟里存储的内容没有关系。
具体而言,当参数分别如下时,sizeof 返回的值表示的含义如下:
a) 数组——编译时分配的数组空间大小;
b) 指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为 4);
c) 类型——该类型所占的空间大小;
d) 对象——对象的实际占用空间大小;

e) 函数——函数的返回类型所占的空间大小。函数的返回类型不能是 void

2. strlen(...) 是函数,要在运行时才能计算。参数必须是字符型指针(char)。

当数组名作为参数传入时,实际上数组就退化成指针了,读到 \0 为止返回长度,而且是不把 \0 计入字符串的长度的。


一些例子

void test1() {    char str[10];    char* str1 = "0123456789";    strcpy( str, str1 );// 字符串 str1 需要 11 个字节才能存放下(包括末尾的 '\0'),而 string 只有 10 个字节的空间,strcpy 会导致数组越界}
void test2() {    char str[10], str1[10];    int i;    for(i=0; i<10; i++)        str1  = 'a';// 编译错误。因为数组名 str1 为 char *const 类型的右值类型,根本不能赋值                    // 再者,即使想对数组的第一个元素赋值,也要使用 *str1 = 'a';     strcpy( str, str1 );// 对字符数组赋值后,使用库函数strcpy进行拷贝操作,strcpy会从源地址一直往后拷贝,直到遇到'\0'为止。所以拷贝的长度是不定的。如果一直没有遇到'\0'导致越界访问非法内存,程序就崩了。}
void test3(char* str1) {    if(str1 == NULL)        return ;    char str[10];    if( strlen( str1 ) <= 10 )// 应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。        strcpy( str, str1 );}
void Test4() {    char *str = (char *) malloc( 100 );    strcpy( str, "hello" );    free( str );    ... //省略的其它语句}//在执行 char *str = (char *) malloc(100); 后未进行内存是否申请成功的判断;//另外,在 free(str) 后未置 str 为空,导致可能变成一个“野”指针,应加上:str = NULL; 
Swap(int* p1, int* p2) {    int *p;// p 没有申请空间就使用,是一个“野”指针,有可能指向系统区,导致程序运行的崩溃    *p = *p1;    *p1 = *p2;    *p2 = *p;}

在函数中为指针char* pStr 申请空间

// 传值调用 OKvoid GetMemory( char **p ) {    *p = (char *) malloc( 100 );}// 引用调用 OKvoid GetMemory(char *&p) {    p = (char *) malloc (100);}// 使用一级指针,不行// 因为作为形参传递后,有p和pStr都指向同一个地方,当malloc后,p就指向了新的地方,而pStr没变void GetMemory( char *p ) {    p = (char *) malloc( 100 );}// 在函数里用数组,不行// 数组为函数内的局部自动变量,在函数返回后,内存已经被释放。char *GetMemory( void ) {    char p[] = "hello world";    return p;}

写出完整版的 strcpy 函数:

char * strcpy( char *strDest, const char *strSrc ) {// 将源字符串加 const,表明其为输入参数    assert( (strDest != NULL) && (strSrc != NULL) );// 对源地址和目的地址加非 0 断言    char *address = strDest;    while( (*strDest++ = * strSrc++) != '\0' );    return address;// 为了实现链式操作,将目的地址返回}

编写类 String 的构造函数、析构函数和赋值函数:

class String {public:    String(const char *str = NULL); // 普通构造函数    String(const String &other); // 拷贝构造函数    ~String(void); // 析构函数    String& operator=(const String &other); // 赋值函数private:    char *m_data; // 用于保存字符串};//===============================================================================// 普通构造函数String::String(const char *str) {    if(NULL == str) {        m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空        assert(NULL != m_data);// 加分点:对m_data加NULL 判断        *m_data = '\0';    } else {        int length = strlen(str);        m_data = new char[length+1];        assert(NULL != m_data);// 若能加 NULL 判断则更好        strcpy(m_data, str);    }}// String的析构函数String::~String(void) {    delete[] m_data; // 或 delete m_data;}// 拷贝构造函数String::String(const String &other) {// 得分点:输入参数为 const 型    int length = strlen(other.m_data);    m_data = new char[length+1];    assert(NULL != m_data);// 加分点:对 m_data 加 NULL 判断    strcpy(m_data, other.m_data);}// 赋值函数String& String::operator=(const String &other) { // 得分点:输入参数为const型    if(this == &other) // 得分点:检查自赋值        return *this;    delete [] m_data;// 得分点:释放原有的内存资源    int length = strlen( other.m_data );    m_data = new char[length+1];    assert(NULL != m_data);// 加分点:对 m_data 加 NULL 判断    strcpy( m_data, other.m_data );    return *this;// 得分点:返回本对象的引用}








0 0
原创粉丝点击