您的位置:首页 > 编程语言 > C语言/C++

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

2016-12-15 23:43 393 查看
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) 为 4;sizeof(Str1) 为 5;sizeof(Str2) 为 6。

char* 指针

不会自动在末尾补 \0

string 型变量

1. 以 \0 结尾(不会自动补  \0)

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

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

重要的:
用字符串初始化指针时,指针指向的是放在常量区的字符串常量;
而用字符串来初始化数组时,是用字符串常量的内容初始化数组的内容;
数组名是一个右值类型( 比如字符型数组的类型为  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 申请空间:
// 传值调用 OK
void GetMemory( char **p ) {
*p = (char *) malloc( 100 );
}

// 引用调用 OK
void 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;// 得分点:返回本对象的引用
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: