构造数据类型(C++)

来源:互联网 发布:5级瓦基丽武神升级数据 编辑:程序博客网 时间:2024/06/07 04:58

[程序设计教程用c++语言编程陈家骏]

一、枚举类型:基本数据类型的值集是由语言定义的,而枚举类型的值集由程序员定义,定义枚举类型时,需要列出值集中的每个值,格式:
enum <枚举类型名> {<枚举值表>};

enum {<枚举值表>} <变量表>;  //省略类型名
枚举值为整形符号常量,
enum Day {SUN=7,MON=1,TUE,WED,THU,FRI,SAT};
enum Color {RED,GREEN,BLUE};
第一个枚举值通常为0,第二个为1,依此类推,如果设置了其中某个值,后面值一次加1,定义变量格式:
<枚举类型名> <变量表>;  //c++格式

enum <枚举类型名> <变量表>; //c格式
对枚举实施赋值操作:
Day d1,d2,d;
d1=SUN; //ok
d2=d1;  //ok
d=RED;  //error,不同枚举类型之间不能赋值
d=10;  //error,Day中没有值与10对应
枚举值对应一个整数,可以把枚举值赋给一个整形变量,但不能把整形数赋给枚举类型变量,因为其中存在不安全因素,但可以通过强制类型转换把整形数赋值给枚举类型变量,但应保证该整形值属于枚举值集范围,否则没意义,可以对枚举类型值实施比较、算术运算,系统首先把它们转换为对应的整形值,然后运算,不能对枚举类型的值直接进行输入、输出,如:
d=(Day) 3;  //ok
d=d+1;   //error,d+1结果为int型
d=(Day) (d+1); //ok
cin>>d;  //error

二、一维数组:一维数组数据,编译程序会在内存中分配连续存储空间,如果数组下标越界,C++不会检测,结果不可预知
1.数组别名定义:
typedef <元素类型> <数组类型名> [<元素个数>];
如:
typedef int A[10];
A a;
2.初始化:
int a[10]={1,2,3,4};  //不足部分初始化为0
int b[]={1,2,3};  //指出b有3个元素,初始值为1,2,3
3.向函数传递一维数组:调用者需要把一维数组变量的名以及数组元素个数传递给被调函数,被调函数形参为不带数组大小的一维数组定义以及数组元素个数,c++中,数组作为函数参数,传递的是数组首地址,形参不会再分配内存空间。如:
int fun(int x[], int num)  //fun可以接受任意的元素类型为int的一维数组
{
……
}
int main()
{
……
int a[10],b[20];
fun(a,10);
fun(a,20);
……
}

三、字符数组:C++中通常用char的一维数组表示字符串,定义字符数组时,其元素个数比实际能存储的字符串最大长度多一个,用来存储字符串结束标记'/0',字符数组作为参数传递给函数时,只需传递数组名而无需字符个数,如
/*当用户输入超过9个字符时,程序会有问题,多余的字符会存储到不属于s的内存空间中,这些空间有可能是其他变量的存储空间,C++编译程序出于效率考虑不检查此类错误,设计者要考虑可能出现的这些问题*/
char s[10];  //s可以表示9个字符和'/0',
cin>>s;
for(int i=0;s[i]!='/0';i++)
{
 if(s[i]>='a'&& s[i]<='z')
  s[i]=s[i]-'a'+'A';
}
cout<<s<<endl;
初始化:
char s[10]={'h','e','l','l','o','/0'};  //显示加'/0'
char s[10]={"hello"};  //自动加'/0'
char s[10]="hello";  //自动加'/0'
char s[]="hello";  //自动加'/0'
C++标准库提供了对字符串操作的函数,在cstring或string.h中声明:
1.int strlen(const char s[]);  //返回字符个数(不包括'/0')
2.char * strcpy(char dst[], const char src[]); //将src复制到dst,并在dst中加上'/0',如果src长度大于dst,结果有问题
size_t strlcpy(char *dst, const char *src, size_t dstsize);  //从src复制至多dstsize-1个字符给dst,结果强制有'/0'结束,返回strlen(src),不是ANSI C,有些编译器不支持,来源于OpenBSD 2.4,很多类Unix系统存在,对应的Windows系统中的函数为strcpy_s函数,该函数加强了对参数合法性的检查以及缓冲区边界的检查,如果发现错误,会返回error或抛出异常
char *strncpy(char dst[], const char src[], int n);  //从src复制n个字符到dst,如果src长度小于n,会在dst字符串后补充很多'/0',如果src长度大于或等于n,只复制n个字符到dst,dst后不加'/0',
安全写法为:
strncpy(path, src, sizeof(path) - 1);
path[sizeof(path) - 1] = '/0';
(摘自网络)strcpy的实现代码:
char * strcpy(char * strDest,const char * strSrc)
{               
 if ((strDest==NULL)||(strSrc==NULL)) //[1]  
        throw "Invalid argument(s)"; //[2]
        char * strDestCopy=strDest;   //[3]
        while ((*strDest++=*strSrc++)!='/0'); //[4]
        return strDestCopy;      
}
错误的做法:
[1]
(A)不检查指针的有效性,说明答题者不注重代码的健壮性。
(B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&& strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。在本例中char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。
(C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字 面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错 误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。
[2]
(A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。
(B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会 被忽略、增强程序的可维护性。 [3]
(A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。
[4]
(A)循环写成while (*strDest++=*strSrc++);,同[1](B)。
(B)循环写成while (*strSrc!='/0') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'/0'。
2.返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。
链式表达式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是错误的。其一,源字符串肯定是已知的,返回它没有意义。其二,不能支持形如第二例的表达式。其三,为了保护源字符串,形参用const限定strSrc所指的内容,把const char *作为char *返回,类型不符,编译报错。
参考资料:高质量C++编程
C标准库提供的一系列C风格字符串的库函数头文件:cstring或string.h,传递给这些标准库例程的指针必须具有非零值,并指向以NULL结束的字符数组中的第一个元素,如果必须使用C风格字符串,使用标准库函数strncat和strncpy比strcat和strcpy函数更安全。
const char *cp1="A string example"; //16个字符,不包括'/0'
const char *cp2="A different string";  //18个字符,不包括'/0'
char large[16+18+2];
strncpy(large,cp1,17); //结尾为'/0'
strncat(large," ",2);  //去掉'/0',加上空格,结尾再加'/0'
strncat(large,cp2,19);  //去掉'/0',附加cp2,结尾再加'/0'
strlen(large)为36
尽量使用C++标准库类型string,标准库负责处理所有的内存管理问题,不必再担心每一次字符串修改涉及到得大小问题,尽量避免使用C风格字符串:
string large=cp1;
large +=" ";
large +=cp2;
另:
void *memcpy( void *dest, const void *src, size_t count );  //copies count bytes of src to dest,memcpy returns the value of dest.in <memory.h> or <string.h>.
void *memset( void *dest, int c, size_t count ); //sets the first count bytes of dest to the character c,in <memory.h> or <string.h>.
int memcmp( const void *buf1, const void *buf2, size_t count );//memcmp function compares the first count bytes of buf1 and buf2 and returns a value indicating their relationship.in <memory.h> or <string.h>.
3.char *strcat(char dst[],const char src[]);
char * strncat(char dst[],const char src[],int n);
4.int strcmp(const char s1[], const char s2[]); //s1==s2->0;s1>s2->正数;s1<s2->负数
int strncmp(cosnt char s1[], const char s2[], int n);

四、二维数组
1.别名定义:
typedef <元素类型> <二维数组类型名> [<行数>] [<列数>];
如:
typedef int A[10][5];
A a;
或者
typedef int A[5];
A a[10];
2.初始化:编译程序分配连续空间存储数组元素,C++中安装行来存储的
int a[2][3]={{1,2,3},{4,5}}; //a[1][2]=0
int a[2][3]={1,2,3,4,5};  //a[1][2]=0;
int a[][3]={{1,2,3},{4,5,6}}; //a有2行,行数由初始值的个数决定
3.二维数组作为函数参数传递:调用者提供二维数组名和行数,被调函数形参为不带数组行数的二维数组定义和行数,如:
/*C++,二维数组作为函数的形参时,必须给出列数,否则编译器无法准确找出元素位置*/
int fun(int x[][5], int lin)
{
……
}
int main()
{
int a[10][5];
……
fun(a,10);
}
如果函数处理的是一维数组,可把二维数组变换为一维数组传给函数,如:
int fun(int x[],int num)
{
}
int main()
{
int a[20][10];
fun(a[0],20*10);
}

五、结构类型:一些不同类型的元素构成的复合数据,可以对相同类型的结构进行整体赋值,相同是指对同一个结构类型定义的变量,定义结构类型时,不能对其成员初始化,因为类型不是程序运行时刻的实体,不占内存空间,为结构类型变量分配连续内存空间,可用sizeof(stu)或sizeof(student)获得空间大小,
struct <结构类型名> {<结构体>}; //结构体成员为任意C++类型(void和本结构除外)
<结构类型名> <变量名表>;  //C++格式

stuct <结构类型名> <变量名表>;  //c格式

struct {<结构体>} <变量名表>;
如:
enum Sex{MALE,FEMALE};
struct Date
{
 int year,month,day;
};
struct student
{
int no;
char name[20];
Sex sex;
Date birth_date;
char birth_place[40];
};
student stu;
stu.no=1; //<结构类型变量>.<结构成员名>,成员名可看做是独立变量,允许的操作由变量类型决定
student stu2={2,"zhangsan",MALE,{1970,12,20},"beijing"}; //初始化
结构类型数据作为函数传递:
void display_student_info(student s)
{
……
}
int main()
{
student s1;
display_student_info(s1);  //默认是值传递,很大的结构类型传递时,可采用指针或引用
}

六、联合类型:c++中表示多种类型数据的类型,联合类型的所有成员占有同一块内存空间,该内存大小为最大成员所需的内存空间大小
union A
{
int i;
char c;
double d;
};
A a;
在程序的运行时刻,对a赋不同的值,如:
a.i=1;
或a.c='A';
或a.d=2.0;
如果对某个成员赋值a.i=12;而以另外一种类型使用这个值,将得不到原来的值,如cout<<a.d;输出不是12.0,整形占4个字节,double占8个字节,但输出a.d时,被解释成前4个字节(存12)和后4个字节一起的值。联合类型的赋值(包括参数传递)是按其整个内存空间赋值,不是按成员赋值。

七、指针:指针变量存储的是另一个变量的地址,有自己的内存空间。许多有用的程序都可不使用数组或指针实现,现代c++程序采用vector类型和迭代器取代一般的数组,采用string类型取代c风格字符串。
1.别名:typedef int * Poniter;
Pointer p,q;
type string * pstring;
const pstring cstr; //相当于:string * const cstr;指向string的const指针
2.空指针NULL,不代表任何内存地址,定义在cstdio或stdio.h中
3.任何类型的指针都可以赋给void *类型的指针变量
4.结构类型的指针变量:(*<指针变量>).<结构成员>或<指针变量>-><结构成员>
5.未初始化的指针变量,如果访问所指变量,可能会造成严重后果。
6.指针与一个整形值相加减,实际加减值由所指变量类型决定。如:
int *p;
double *q;
p++; //p的值加sizeof(int)
q -=4;  //q的值减4*sizeof(double)
7.int a[10];
p=&a[0];
可以采用*(<指针变量>+<整形表达式>)访问数组元素,如*(p+2);或者p[2]也可以
8.int x=1;
int *p=&x;
cout<<p;  //输出x的地址,即p的值
cout<<*p;  //输出x的值
char str[]="ABCD";
char *p=&str[0];
cout<<p;  //输出p指向的字符串,即"ABCD"
cout<<*p; //输出A
cout<<(void *)p;  //输出p的值,即"ABCD"的首地址
string s("hello");
string *sp=&s;  //ok

cout<<sp; //ok,输出s的地址

cout<<s;  //ok,输出hello
cout<<*sp;  //ok,输出hello
*sp="goodbye";  //ok,s的值被修改为goodbye

9.c++中,指针作为形参的作用:
a.提高参数传递效率
b.通过形参改变实参的值,如果不想产生函数的副作用(改变非局部变量的值),可以把形参定义成const <类型> * <指针变量>;如:
const int *p;
const in x=0;
p=&x; //ok
*p=1;  //error

int y;
p=&y;  //ok
*p=1;  //error,从形式上看,指向常量的指针变量只能指向常量,但从实际意义上讲可以指向变量,但不能通过它改变所指向变量的值而已
y=1;  //ok

const int z=0;
int *p;
p=&z;  //error,指向变量的指针变量,不能指向一个常量

int x,y;
int * const p=&x;  //定义了一个指针类型的常量p,它指向一个变量,必须要初始化
*p=1; //ok,*p是一个变量
p=&y;  //error,p是一个常量,其值不能被更改

int x,y;
const int * const p=&x;
*p=1;  //error
p=&y;  //error
10.指针作为函数返回值时,不能是局部变量的地址,因为函数返回时,局部量的内存空间被收回,如果调用者在使用这个内存空间中的值之前调用了某个函数,该空间可能被调用的新函数占用并拥有了新值,调用者就得不到原来的值了。
11.动态变量:动态变量分配在堆区中,没有名字,需要通过指针访问变量
创建动态变量:new和malloc
a.new <类型名>或new <类型名>[<第一维大小>]…[<第n维大小>]
string *psa=new string[10];  //动态分配数组时,如果数组元素具有类类型,将是使用默认构造函数,实现初始化
int *pia=new int[10];  //动态分配数组时,如果数组元素是内置类型,则无初始化
int *pia2=new int[10]();  //跟在数组长度后的一对空圆括号,对数组元素做值初始化,元素被设置为0
b.void *malloc(unsigned int size):分配大小为size的内存空间,返回内存首地址,类型为void *,需强制转换返回类型以满足具体的类型分配,如:
double *p=(double *)malloc(sizeof(double));
c.动态变量的撤销:delete和free,delete会自动调用对象类的析构函数,free不会,释放后应赋值为NULL,如:
delete []pia;
free(p);
d.new与malloc区别:
(1)new自动计算所需分配的空间大小,malloc需显示指出
(2)new自动返回相应类型的指针,malloc要做强制类型转换
(3)创建的是动态对象,new会调用相应对象类的构造函数,malloc不会

12.指针声明风格:
a.一个声明语句只声明一个变量,*紧挨着类型放置,强调一个声明语句定义的是一个指针
b.一条声明语句中声明多个指针,*靠近变量标识符放置(推荐)
13.int *p[4];// 指针数组,每个数组成员是指针变量
int (*p)[4];//p是指针变量,指向包含4个整形元素的一维数组

#include <iostream>
using namespace std;
int main()
{

//int* p[2] 是一个指向int型的指针数组
 int* p[2];
 int a[3] = {1, 2, 3};
 int b[4] = {4, 5, 6, 7};
 p[0] = a;
 p[1] = b;
 for(int i = 0; i < 3; i++)
  /*cout << *(p[0] + i);*/cout << **p + i;//输出123
 cout<<endl;
 for(i = 0; i < 4; i++)
  cout << *p[1] + i;// cout << **p + i;//输出4567
 cout<<endl;

 

//对于int (*p)[2], 它相当于一个二维数组的用法,只是它是一个n行2列的数组

 int (*q)[2];
 int c[3][2] = {{1, 2}, {3, 4}, {5, 6}};
 q = c;
 int j = 0;
 for(i = 0; i < 3; i++)
  for(j = 0; j < 2; j++)
   /*cout << q[i][j]; */cout << *(*(q+i)+j);//输出123456
 cout << endl;
 return 0;
}

14.函数指针:<返回类型> (*<指针变量>) (<形式参数表>),注意与返回指针的函数区别,如<返回类型>* <函数名>(<形式参数表>),另外,函数别名定义:
typedef <返回类型> (*<指针变量>) (<形式参数表>);如:
typedef double (*FP)(int );
FP fp;

Fp func_list[10];//或者double (*func_list[10])(double);

15.如果一个指针变量没有初始化赋值,访问它所指向的变量将会导致运行时刻的严重错误。指针的作用:访问动态变量,提供参数传递效率,解决函数多个返回值问题,提高对数组访问效率和灵活性,但使得程序可靠性下降及书写繁琐等

16.引用与指针区别:
a.引用和指针都可以实现通过一个变量访问另一个变量,但引用采用直接方式,指针采用间接方式
b.除了在定义时指定引用变量外,引用类型不能再引用其它变量,而指针可以指向其它同类型的变量,引用比指针要安全
c.作为函数参数类型时,引用类型参数的实参是一个变量的名字,而指针类型参数的实参是一个变量的地址

17.sizeof:结果返回一个对象或类型的长度,使用sizeof的结果部分地依赖所涉及的类型:

(1)对char类型或值为char类型的表达式,sizeof结果为1

(2)对引用类型做sizeof,返回存放此引用类型对象所需空间的内存大小

(3)对指针做sizeof,返回存放指针所需的内存大小,如果要获取该指针所指向对象的大小,必须对该指针进行解引用

(4)对数组做sizeof,等效于对其元素类型做sizeof的结果乘以数组元素的个数

原创粉丝点击