字符串
- 字符串的表示
三种理解角度:作为字符数组,作为指向字符的指针,作为抽象的字符串整体
- 字符数组
多个字符数组连续存储时候的问题:无法区分存储空间刚好连续的多个字符数组。
== 解决方案==:字符数组末尾添加结束标志’\0’
示例:
char s[9] = {‘C’, ‘P’, ‘P’,’-’,‘P’,‘r’,‘o’,‘g’,’\0’};
char t[6]={‘H’,‘e’,‘l’,‘l’,‘0’,’\0’};
优点:可以在程序运行时侯通过测试’\0’字符确定字符数组是否结束,而不需要了解数组元素个数,使处理元素各数未知的数组成为可能通过指针运算直接操作字符数组中的字符,而不再使用数组格式访问字符元素。
示例:
编写程序,返回字符c在字符串s中的首次出现位置
//法1
unsigned int FindCharFirst(char c, char s[])
{
unsigned int i;
if(!s);
{
cout<<"FindCharFirst:Illegal string.\n";
exit(1);
}
for(i=0;s[i]!='\0';i++)
{
if(s[i]==c)
return i;
}
return inexistent_index;//0xffffffff
}
//法2
unsigned int FindCharFirst(char c, char*p)
{
char*t = p
if(!p)
{
cout<<"FindCharFirst:Illegal string.\n";
exit(1);
}
for(t=s;*t!='\o';t++)
{
if(*t==c)
return t-p;
}
return inexistent_index;//oxffffffff
}
-
抽象字符串
抽象字符串的定义
typedef char* STRING;
typedef const char* CSTRING; -
字符数组与字符指针的差异
字符数组量的定义、初始化与存储
char s[9]== {‘C’, ‘P’, ‘P’,’-’,‘P’,‘r’,‘o’,‘g’,’\0’};
字符指针量的定义、初始化与存储
chars = “CPP-Prog”;
后者存在单独的指针变量,前者则没有
a. 按照指针格式定义字符串,可以直接赋值
示例:chars;s=“CPP-Prog”;//正确
原因:字符串文字首先分配空间,然后将其基地址赋给s,使s指向该字符串基地址
b. 按照字符数组格式定义字符串,不能直接赋值
示例:char s[9]; s=“CPP-Prog”;//错误
但是char s[9]=“CPP-Prog”;//正确
不能对数组进行整体赋值操作
原因:数组空间已分配,字符串文字空间已分配,且他们位于不同位置,不能直接整体赋值
c. 返回字符串的函数
编写函数,将某个字符c转换为字符串
//正确
STRING TransformCharIntoString(char c)
{
STRING _s=(STRING)malloc(2);
_s[0]=c;
_s[1]='\0';
return _s;
}
//错误
char *TransformCharIntoString(char c)
{
char _s[2];
_s[0]=c;
_s[1]='\0';
return _s;
}
/*错误函数定义
对于所有返回值为指针类型的函数,都不能返回在函数内部定义的局部数据对象--所有局部对象在函数结束后都不再有效,其地址在函数返回后没有意义
*/
-
标准字符串库
标准库中关于字符串处理的函数很多,均定义于头文件"cstring"中
常用字符串函数
char* strcat(chardest, const charsrc);
char* strcpy(chardest, const charsrc);
int strcmp(const chars1, const chars2);
int strlen(const chars);
charstrtok(chartoken,const chardelimiters);
不建议使用标准字符库,使用string类替代 -
string类
a. 定义于头文件"string"中
b. 声明与构造string对象
string s=“abcdefg”;
string s(“abcdefg”);
c. 读取与写入string对象
cout<<s<<endl;
cin>>s; //读取以空格、制表符与回车符分隔的单词
getline(cin, s, ‘\n’);//读取包含空格和制表符在内的整行
d. 获取对象的长度
string s = “abcdefg”;
int a = s.length();
e. 改变string对象的容量大小
s.resize(32);//将s设置为32字符长,多余舍弃,不足空闲
s,resize(32,"=");//多余舍弃,不足补“=”
f. string对象的追加操作
string s1=“abcd”, s2=“efg”;
s1.append(s2);//将字符串s2追加到s1尾部
s1 += s2;//同上
g. string对象的比较操作
string s1=“abcdefg”, s2=“abcdxyz”;
int a=s1.compare(s2,0);//从0号位字符开始比较
h. string对象的查找操作
string s1=“abcdefg”, s2=“bcd”;
int a=s1.find(s2,0); //从字符串开头开始查找,结果为s2在s1中首次出现的位置 -
动态存储管理
a. 内存分配与释放
1)静态内存分配方式
适用对象:全局变量与静态局部变量
分配与释放时机:在程序运行前分配,程序结束时释放
2)自动内存分配方式
适用对象:普通局部变量
分配与释放时机:在程序进入该函数或该块时自动进行,退出时自动释放
3)动态内存分配方式
适用对象:匿名数据对象(指针指向的目标数据对象)
分配与释放时机:在执行特定代码段时按照该代码段的要求动态分配和释放
动态内存分配的目的:静态与自动内存分配方式必须事先了解数据对象的格式和存储空间大小,部分场合无法确定数据对象的大小。
示例:声明一个包含n个元素的整数数组,n值由用户在程序执行时输入。编译时程序未执行,不知道n值!
动态内存分配的位置:计算机维护的一个专门存储区:堆 所有动态分配的内存都位于堆中
动态内存分配的关键技术:使用指针指向动态分配的内存区,使用引领操作符操作目标数据对象
4)标准库中的动态存储管理函数
动态存储管理函数的原型:头文件:“cstdlib”和“cmalloc”,两者包含其一即可
内存分配函数原型:void* malloc(unsigned int size);
内存释放函数原型:void free(void* memblock);
- void类型
特殊的指针类型,指向的目标数据对象类型未知,不能在其上使用引领操作符访问目标数据对象,可以转换为任意指针类型,不过转换后类型是否有意义要看程序逻辑,可以在转换后的类型上使用引领操作符
主要目的:作为一种通用指针类型,首先构造指针对象与目标数据对象的一般性关联,然后由程序员在未来明确该关联的性质。
6)malloc函数的一般用法
首先定义特定类型的指针变量:char p;
调用malloc函数分配内存:p=(char)malloc(11);
参数表示所需要分配的存储空间大小,以字节为单位
例子:若要分配能够保存10个字符的字符串,分配11个字节(字符串结束标志也要分配空间)
将返回值转换为char类型赋值给原指针,使p指向新分配空间的匿名目标数据对象
示例1:
//编写函数,复制字符串
char *DuplicateString(char*s)
{
char *t;
unsigned int n, i;
if(!s){cout<<"DuplicateString:Parameter Illegal"<<endl;exit(1);}
n=strlen(s);
t = (char*)malloc(n+1);
for(i=0;i<n;i++)
t[i]=s[i];
t[n]='\0';
return t;
}
- free函数的一般用法
传递一个指向动态分配内存的目标数据对象的指针
示例一:charp;p=(char)malloc(11);free(p);
示例二:int* p=(int*)malloc(10sizeof(int));free(p);
示例二分配能够容纳10个整数的连续存储空间,使p指向该空间的基地址,最后调用free函数释放p指向的整个空间。
free函数释放的是p指向的目标数据对象的空间,而不是p本身的存储空间,调用free函数后,p指向的空间不再有效,但p仍然指向它。为保证在释放目标数据对象空间后,不会再次使用p访问,建议按照以下格式书写代码:free(p) ; p=NULL;
特别说明:有分配就有释放
8)new/new[]操作符
1、动态创建单个目标数据对象:
分配目标对象:intp; p=new int; p=10;
分配目标对象:intp;p=new(int);p=10;分配目标对象并初始化:intp;p=new int(10);//将p初始化为10
分配目标对象并初始化:intp;p=new(int)(10);
2、动态创建多个目标数据对象
分配数组目标对象:int*p;p=new int[8];//分配8个元素的整数数组
9)delete/delete[]操作符
1、释放单个目标数据对象
释放目标对象:int *p;p=new int;*p=10;delete p;
2、释放多个目标数据对象:int p;p=new int[8]; delete [] p;
不是delete p[]
10)指针使用的一般原则
主动释放原则:如果某函数动态分配了内存,在函数退出时该目标数据对象不再需要,应主动释放它,此时malloc与free在函数中成对出现。
所有权转移原则:如果某函数动态分配了内存,在函数退出后该目标数据对象仍然需要,此时应将所有权转交给本函数之外的同型指针对象,函数内部代码只有malloc,没有free
11)空悬指针与所有权
空悬指针问题:
所有权的重叠:指针赋值操作导致两个指针数据对象指向同样的目标数据对象,即两个指针都声称“自己拥有目标数据对象的所有权”
示例:intp,q;q=(int)malloc(sizeof(int));p=q;
产生原因:如果在程序中通过某个指针释放了目标数据对象,另一个指针并不了解这种情况,它仍只想不再有效的目标数据对象,导致空悬指针
示例:free(p);p=NULL://q为空悬指针
-
引用类型
1、 引用的定义
定义格式:数据类型& 变量名称=被引用变量名称;
示例:int a; int &ref=a;
2、引用的性质
引用类型的变量不占用单独的存储空间,为另一个数据对象起个别名,与该对象同享存储空间
引用类型的变量必须在定义时初始化,此关联关系在引用类型变量的整个存续期都保持不变,对引用类型变量的操作就是对被引用变量的操作
示例://编写程序,使用引用操作目标数据 #include <iostream> using namespace std; int main() { int a; int &ref=a; a = 5; cout<<"a: "<<a<<endl;cout<<"ref: "<<ref<<endl; ref=8; cout<<"a: "<<a<<endl;cout<<"ref: "<<ref<<endl; return 0; }
3、引用作为函数参数
引用的最大意义:作为函数参数
参数传递机制:引用传递,直接修改实际参数值
使用格式:返回值类型 函数名称(类型& 参数名称);
函数原型示例:void swap(int&x, int&y);
函数实现示例:void swap(int&x, int&y){int t;t=x;x=y;y=t;return;}
函数调用示例:int main(){int a=10,b=20;swap(a,b);return 0;}
4、函数作为函数返回值
常量引用:仅能引用常量,不能通过引用改变目标对象值;引用本身也不能改变引用对象
引用作为函数返回值时不生成副本
函数原型示例:int &Inc(int &dest, const int &alpha);
函数实现示例:int &Inc(int &dest, const int&alpha){dest+=alpha;return dest;}
函数调用实例:引用类型返回值可以递增
int main(){int a=10, b=20,c;Inc(a,b);c=Inc(a,b)++;return 0;}