什么是复合类型?
我在之前说过,具体的定义我说不出来,不过可以怎么理解,复合类型比基本类型高级一些,它可以存储别的变量。你要知道通过基本的数据类型的是无法完成什么大事情的。
C++ 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
extern 关键字用于数组
char* p="123";
int extern char p[];
……
cout<<p[1]<<endl;//会输出什么呢。大家可以试试。反正不是2
上面设计到了编译底层方面的知识,如果感兴趣给大家推荐《 程序员的自我修养: 链接、装载与库》这本书
定义数组
在 C++ 中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:
type arrayName [ arraySize ];
表达式arraySize 是指定元素数目,他必须是常量(1,2,3...或const值),也可以时常量表达式的值(2*sizeof(int))。即其中所有的值在编译时都是已知的,具体的说arraySize不能是非常量的值,非常量的值的值是在运行的时候设置的。
现在我们来说一说编译时期已知这个话题为什么上面说到的const 和sizeof可以初始化数组呢,首先我们要知道堆栈(后面章节说),堆中的数组大小是要在编译时确定的,要不压栈出栈不知道分配多少内存,而上述可行的原因是编译器的编译优化所得,编译器知道这些东西是亘古不变的,所以在编译器时期就运算完了,而不是等到运行时再运算。
下表列出了所有的可能(有点像谭浩强的书了,不过说实在这种方式还是不错的,个人观点)
示例:
int out_1=3;//#1 ok
const int out_2=3;//#2 ok
static int out_3=3; //#3 ok
static const int out_4=3;//#4 ok
std::string out_5="32";//#5 ok
int out_6[3];#6 ok
int test_1 [out_1];//[Error] array bound is not an integer constant before ']' token
int test_2 [out_2];//ok
int test_3 [out_3];//[Error] array bound is not an integer constant before ']' token
int test_4 [out_4];//ok
int test_5 [out_5];//[Error] array bound is not an integer constant before ']' token
int test_6 [out_6];//[Error] array bound is not an integer constant before ']' token
对于test_6的报错信息,大家可以区看指针小节
type为数组中存储的数据类型,它可以是任意有效的 C++基本类型(基本类型,类,引用,指针,或是除数组以外的复合类型)例如,要声明一个包含 10 个枚举变量的数组ENUM,声明语句如下:
enum A {AA,BB,CC};A a[100];
现在 ENUM是一个可用的数组,可以容纳 10 个 枚举变量了。但是你不可以怎么声明
Tips:
数组的存储类型是不可以为数组的,但是可以存储指向数组的指针。
int [3] a[10];//error
上面是语法错误,为什么会这样呢 ,大概原因是编译器的语法分析机制所致吧。
Notet:
数组变量的类型可以是除数值以为的所有类型(各种指针,出数值以为的各种腹黑类型,各种类),且对于非new产生的数组一定要让数组大小在编译时就确定。
初始化数组
基础类型
初始化方面 请看下面
int g_iaTest1[2]={0,1};//正常初始化,初始化的时候为每个元素赋值
int g_iaTest2[2]={0};//零初始化,将所有类型转对应的“0”
int g_iaTest3[10]={1,2,3,4};//前面 4个对应的初始化,后面全是0
int g_iaTest4[]={1.2}//还有一点你就是可以不写数组元素数目 ,让编译器来决定由多少个。这种方式多用于字符串
如果不理解上面为什么1.2 可以赋值给存储int类型的数组,看一看查看类型转换专题
复合类型
结构(共用体同理)
struct A {
int a;
double b;
};
struct A struct_test[3]=
{
{1,1.2},//大括号了初始化
{1,1.2},
{1,1.2},
};
枚举
enum A {AA,BB,CC};
enum A ENUM_TEST[3]={AA,BB,CC};//直接为枚举变量赋值
int integer_arrary[3][3]={
{1,2,3},//对多维数组赋值,详情请看下面
{1,2,3},
{1,2,3},
};
c++中数组是不可以定义之后在初始化,但是可以定义之后一个一个的写入值,也不能把一个数组赋值给另一个数组。因为数组名表示第一项的地址,且是const的
int g_iaTest1[2]={0}//OK
int g_iaTest2[2];//OK
g_iaTest2[1]=0;//OK
g_iaTest2[2]={0};//#3 错误
g_iaTest2=g_iaTest1//#4错误
数组下标从0开始且编译器不会检查下标的有效值a[10]也是可以的。
int g_iaTest[2];//下表0~1
g_iaTest[2]=100;//下标2不再数组中但是可以赋值
c++数组和处理指针基本上是等价的 c++中把数组名解析为数组第一个元素的地址,而对数组取地址是整个数组的地址(数组取地址就像int (*)[10])
有数组 int a[10] ,其中a[1]被看作为 *(a+1),a[2]被看作*(a+2)
多维数组
一般情况下我们使用二维数组是普遍的,用它来描述2维、3维的坐标等等,但是是为了让大家能理解多维数组我们会拿3维数组举例:
#include <iostream>
using namespace std;
int main ()
{
int array3_1[3][3][3]={};//定义和“零”初始化
for(int i=0;i<3;i++)//赋值
for(int j=0;j<3;j++)
for(int k=0;k<3;k++)
array3_1[i][j][k]=2;
int array3_2 [3][3][3]={ //定义的时候初始化
{
{3,3,3},
{3,3,3},
{3,3,3},
},
{
{3,3,3},
{3,3,3},
{3,3,3},
},
{
{3,3,3},
{3,3,3},
{3,3,3},
},
};
}
其实在c++中中是没有多维数组这个概念的。其实只有一维数组,多维数组的实现是编译器更具我们的[]数量自行生成的。比如上面的3维数组array3_2。
array3_2是一个数组其中存储的是指向数组_2的指针_2,而数组_2中存储着指向数组_3的指针_3。数组_3存储的就是int了,如下图
指针表示多维数组
其实c++是使用指针来管理数组的。指针表示多维数组你要知道以下几点
1、c++是不存在多维数组,底层是通过指针实现的
2、数组名是一个常量(不允许更改常量),其代表数组首元素的首地址。
3、对数组的数组名进行取地址(&)操作,其类型为整个数组类型的大小
4、数组底层由指针表示。int a[3][2]相当于*(*(a+3)+2) 。
5、指针作为多维数组的定义必须具有除第一个之外的所有维的边界。
Tips:
第5条的其实和第4是一个概念。第五条是为了不让第四条出现奇异。
char a [3];
char *p=a;//ok,根据第二条,编译器知道一个元素(char)的长度,所以 p++,和a++ 都是让指针移动一个char的距离。
char (*p)[]=&a//errror,根据第三条,(&a)++移动3个char的距离,p是一个指向不知道多长的数组,所以p++不知道移动多少个char的距离
所以下面也是错误的
char (*p)[] ;//ok
p++;//error,不知道移动多少个char 距离
所以我们大体知道为什么在函数形参和实参 char * p 和char a [],char a [] [3]和char(*)p[3]都是等价的了吧
一位数组
char a[3];
char * p1 = a;//相当于char * p = &a[0],看第二条
char (*p2)[3] = &a;//看第三条
二维数组
char a[3][2];
char (*p1)[2] = a;//a为第一维数组的数组名,类型为char (*)[2]
char * p2 = a[0];//a[0]维第二维数组的数组名,类型为char *
char (*p3)[3][2] = &a;
TIps:
数组边界可以让编译器决定哦,
char a[3][3]={
{1,2,3},
{1,2,3},
{1,2,3},
};
//上面和下面等价
char a[3][3]={1,2,3,1,2,3,1,2,3,};
//所以就解释了:指针作为多维数组的定义必须具有除第一个之外的所有维的边界。下面是错误的
char a[][]={//error
{1,2,3}
};
三维数组
char a[3][2][2];
char (*p1)[3][2][2] = &a;//对数组名取地址类型为整个数组
char (*p2)[2][2] = a;
char (*p3) [2] = a[0];//或者a[1]、a[2]
char *p4 = a[0][0];//或者a[0][1]、a[1][0]...
数组的代替品(vector,array)
vector,array都是标准库中的类,之所以有这两个类是因为,普通的数组在真正的使用中有一些力不从心的地方,所有就有人发明了这两个类(不懂概念的可以先跳过)
vector 是用new和delete分配数组,可自动自动调节大小
#includ<vector>
using namespace std
int main(){
vector<int > a1;//定义vector变量
vector<double> a2(size);//构造函数 构造存储大小 size可以是变量
}
其中的size可以是变量 ,因为size是构造函数的参数
array 固定大大小也是存储在栈中,效率和数组一样但是比数组更方便安全
#include<array>
using namespace std
int main(){
array<int ,3> a1;
array<double ,2> a2={"1","2"};
}
无论是数组还是这两种代替类都可以用数组表示法访问个元素,而array,数组,是存储在栈中的,但是vector是在堆中的,其中array,vector和数组的区别在于数组可以出现a[-2],你没看错c++数组是不检查数组下标的有效值的,但是array和vector不同 如果你想保持数组越界的特性就可以用数组表示法访问,如果不想就可以使用at()函数,他会捕获越界异常,但是这样的代价是运行代码效率会被捕获异常降低,所以存在不捕获越界的访问方法,还有一点就是这两个类都有begin()和end()方法来确定边界
访问数组元素
数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如:
double salary = balance[9];
上面的语句将把数组中第 10 个元素的值赋给 salary 变量。下面的实例使用了上述的三个概念,即,声明数组、数组赋值、访问数组:
#include <iostream>
using namespace std;
#include <iomanip>
using std::setw;
int main ()
{
int n[ 10 ]; // n 是一个包含 10 个整数的数组
// 初始化数组元素
for ( int i = 0; i < 10; i++ )
{
n[ i ] = i + 100; // 设置元素 i 为 i + 100
}
cout << "Element" << setw( 13 ) << "Value" << endl;
// 输出数组中每个元素的值
for ( int j = 0; j < 10; j++ )
{
cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl;
}
return 0;
}
上面的程序使用了 setw() 函数来格式化输出。当上面的代码被编译和执行时,它会产生下列结果:
Element Value
0 100
1 101
2 102
3 103
4 104
5 105
6 106
7 107
8 108
9 109
Tips:
数组的知识远不止这些,上面都是最基本。大多数情况下数组的使用也就如此,但是如果数组中存放的是函数指针,多重嵌套的数据结构等,这时候数组的知识点就包括它所存储的类型的知识点了,所以我尽可能在别的小节讲解
结构
C/C++ 数组允许定义可存储相同类型数据项的变量,但是结构是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
extern用于结构
struct strcut_test {
int a;
double b;
}
struct strcut_test a;//定义的时候可以写struct也可以不写
static strcut_test b;//static 变量章节会说
extern struct strcut_test a;//ok;
extern strcut_test b;//变量章节会说
定义结构
为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct [structure tag]
{
member definition;
member definition;
...
member definition;
} [one or more structure variables];
structure tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在结构定义的末尾,最后一个分号之前,您可以指定一个或多个结构变量,这是可选的。下面是声明 Book 结构的方式:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}book;
Tips:
siezof一个结构的大小是其中所有类型sizeof的和
访问结构成员
.
为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。您可以使用struct 关键字来定义结构类型的变量。下面的实例演示了结构的用法:
#include <iostream>
#include <cstring>
using namespace std;
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; // 声明 Book1,类型为 Book,其实可以不用写struct的,但是为了让大家明白这是一个结构所以我们使用了struct
struct Books Book2; // 声明 Book2,类型为 Book
// Book1 详述
strcpy( Book1.title, "Learn C++ Programming");
strcpy( Book1.author, "Chand Miyan");
strcpy( Book1.subject, "C++ Programming");
Book1.book_id = 6495407;
// Book2 详述
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Yakit Singha");
strcpy( Book2.subject, "Telecom");
Book2.book_id = 6495700;
// 输出 Book1 信息
cout << "Book 1 title : " << Book1.title <<endl;
cout << "Book 1 author : " << Book1.author <<endl;
cout << "Book 1 subject : " << Book1.subject <<endl;
cout << "Book 1 id : " << Book1.book_id <<endl;
// 输出 Book2 信息
cout << "Book 2 title : " << Book2.title <<endl;
cout << "Book 2 author : " << Book2.author <<endl;
cout << "Book 2 subject : " << Book2.subject <<endl;
cout << "Book 2 id : " << Book2.book_id <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Book 1 title : Learn C++ Programming
Book 1 author : Chand Miyan
Book 1 subject : C++ Programming
Book 1 id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Yakit Singha
Book 2 subject : Telecom
Book 2 id : 6495700
->
详情请看指针
struct strcut_test {
int a;
double b;
} ;
struct strcut_test a;
struct strcut_test *p=&a;
p->a=100;
结构初始化
上面的案例初始化书写太慢了,一般人是不会怎么写的。所以就有了下面像数组一样的初始化了
#include <iostream>
#include <cstring>
using namespace std;
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1={
"Learn C++ Programming",
"Chand Miyan",
"C++ Programming",
6495407,
};
struct Books Book2={
"Telecom Billing",
"Yakit Singha",
"Telecom",
6495700,
};
// 输出 Book1 信息
cout << "Book 1 title : " << Book1.title <<endl;
cout << "Book 1 author : " << Book1.author <<endl;
cout << "Book 1 subject : " << Book1.subject <<endl;
cout << "Book 1 id : " << Book1.book_id <<endl;
// 输出 Book2 信息
cout << "Book 2 title : " << Book2.title <<endl;
cout << "Book 2 author : " << Book2.author <<endl;
cout << "Book 2 subject : " << Book2.subject <<endl;
cout << "Book 2 id : " << Book2.book_id <<endl;
return 0;
}
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}Book1={
"Learn C++ Programming",
"Chand Miyan",
"C++ Programming",
6495407,
}, Book2={
"Telecom Billing",
"Yakit Singha",
"Telecom",
6495700,
};
结构作用域
#include <iostream>
#include <cstring>
using namespace std;
//外部声明 全局可以访问
struct Books_out
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
void method();
int main( )
{
//局部声明的
struct Books_in
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
struct Books_out Book_out; // 声明 Book_out,类型为 Book_out
struct Books_in Book_in; // 声明 Book_in,类型为 Book_in
return 0;
}
void method(){
struct Books_out Book_out; // ok 因为Book_out 是全局的
struct Books_in Book_in; // errot 因为Book_in是局部的,现在method不知道Book_in 是什么
}
结构中的位字段
struct A{
int a:4
int :4//间距
bool c:1
bool d:1
};
A a{1,true,false}//初始化 间距没法访问
a.c//正常访问
结构作为函数参数
您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用上面实例中的方式来访问结构变量:
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; // 声明 Book1,类型为 Book
struct Books Book2; // 声明 Book2,类型为 Book
// Book1 详述
strcpy( Book1.title, "Learn C++ Programming");
strcpy( Book1.author, "Chand Miyan");
strcpy( Book1.subject, "C++ Programming");
Book1.book_id = 6495407;
// Book2 详述
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Yakit Singha");
strcpy( Book2.subject, "Telecom");
Book2.book_id = 6495700;
// 输出 Book1 信息
printBook( Book1 );
// 输出 Book2 信息
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
cout << "Book title : " << book.title <<endl;
cout << "Book author : " << book.author <<endl;
cout << "Book subject : " << book.subject <<endl;
cout << "Book id : " << book.book_id <<endl;
}
当上面的代码被编译和执行时,它会产生下列结果:
Book title : Learn C++ Programming
Book author : Chand Miyan
Book subject : C++ Programming
Book id : 6495407
Book title : Telecom Billing
Book author : Yakit Singha
Book subject : Telecom
Book id : 6495700
指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
struct_pointer->title;
让我们使用结构指针来重写上面的实例,这将有助于您理解结构指针的概念:
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books *book );
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; // 声明 Book1,类型为 Book
struct Books Book2; // 声明 Book2,类型为 Book */
// Book1 详述
strcpy( Book1.title, "Learn C++ Programming");
strcpy( Book1.author, "Chand Miyan");
strcpy( Book1.subject, "C++ Programming");
Book1.book_id = 6495407;
// Book2 详述
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Yakit Singha");
strcpy( Book2.subject, "Telecom");
Book2.book_id = 6495700;
// 通过传 Book1 的地址来输出 Book1 信息
printBook( &Book1 );
// 通过传 Book2 的地址来输出 Book2 信息
printBook( &Book2 );
return 0;
}
// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
cout << "Book title : " << book->title <<endl;
cout << "Book author : " << book->author <<endl;
cout << "Book subject : " << book->subject <<endl;
cout << "Book id : " << book->book_id <<endl;
}
当上面的代码被编译和执行时,它会产生下列结果:
Book title : Learn C++ Programming
Book author : Chand Miyan
Book subject : C++ Programming
Book id : 6495407
Book title : Telecom Billing
Book author : Yakit Singha
Book subject : Telecom
Book id : 6495700
Tips:
结构之间是可以用指针相互指引的
struct B{
int i;
struct A* a;
};
class A{
int i;
struct B* b;
};
但是你不可以
struct B{
int i;
struct A a;
};
class A{
int i;
struct B b;
};
原因有二
1报错 [Error] field 'a' has incomplete type 'A',吐过你把A放在上面又会报错[Error] field 'b' has incomplete type 'B'
2其次如果这样可以话 将会出现无限循环A中有B ,B中有A,A中有B...
共用体
共用体定义
union A{
int a;
char b;
double c;
}
上述公用体每次只能存储a,b,c其中一个。
int test1=1;
char test2='A';
double test3=1.2;
union union_test{
int data_a;
char data_b;
double data_c;
}a;
a.data_a=test1;
a.data_b=test2;
a.data_c=test3;
std::cout<<sizeof (union_test)<<std::endl;//8bytes
由于共用体只能存在其中一个值,所以共用体的长度位最大类型的长度
匿名共用体
#include <iostream>
#include <cstring>
using namespace std;
struct widget{
char brand[20];
int type;
union{
long id_num;
char id_char[20];
};
};
int main( )
{
widget prize;
if(prize.type==1)
cin>>prize.id_num;
else
cin>>prize.id_char;
return 0;
}
上面c++结构结构widget中有一个匿名共用体,因为共用体中的所有类型都是同一个地址,所以可以不用标识符。直接当中结构widet的变量。这种做法通常(不只)用于节省内存。比如嵌入式开发
枚举
声明枚举
枚举的声明很特殊是因为他如果想对它使用extern 必须要在同一文件文件中声明它。示例如下
//test_h
#fndef TEST_H
#define TEST_H
enum A{AA,CC,DD};
#ifend
//file1
#include"test_h"//包含了枚举A的声明
enum A a;
//file2
#include"test_h"
extern enum A a;//必须要有枚举A的声明,否则会报错不知道A长什么样子
int main(){
return 0;
}
定义枚举
enum A{B,C,D};//定义
A a1//声明变量
使用枚举
enum A{ B,C,D,E }; A a=C; if(a==B) cout<<"hello word"<<endl;
enum A{ B,C,D,E }; //定义没有名字的枚举可以直接用枚举量 int a=B; cout<<a<<endl;//output 0
枚举变量的赋值
枚举量对应着0到n的数字,不强转换的情况下,只能把枚举量赋值给枚举变量也不能把数字赋值给枚举量
enum A{B,C,D,E};
A a;
A b=B;
a=b;//ok
a=1;//not allowed
枚举运算
枚举变量参与运算时,枚举量才会被提升为int ,其他情况下不会自动转换的。而int 不在强制转换下,不能自动转换为枚举 ,且枚举变量不能运算)
#include <iostream>
#include <cstring>
using namespace std;
void f(int);
int main( )
{
enum A{B,C,D,E};
A a1;
A a2;
int a=1;
cout<<a1+1<<endl;//ok 枚举变量参与运算时可以被提升转换为int
a2=1;//error int不能被自动转换为枚举变量
a1++;//error 枚举变量不能运算
cout<<a1<<endl;//OK,不是说不可以吗,这个原因说到函数传递参数的时候你就明白了
f(a1);;// ok 和上面一样
}
void f(int a){
cout<<a<<endl;
}
设置枚举的值
menu A{a,b=0,c,d=1}//a,b为0,c,d为1
menu A{a,b=100,c,d}//a=0,b=100,c=101,d=102
枚举的取值范围
枚举范围最大值大于枚举量的最小2次幂值-1,最小值如果不小于0则为0 如果负数 则为小于最小值 的2次幂+1
《c++ primer plus》第五版上面说,在c++中可以通过强制转换将 在枚举范围内的任何整型赋值枚举变量,即使这个值不是枚举。但是我在dev c++ 5.11中这句话好像不怎么灵验。
enum A{a=1,b=2,c=4,d=8}; //最小值为0,最大值为15;
A a1=A(6);//ok
A a2=A(100);//ok
A a3=A(-100);//ok
cout<<a1<<endl;//ok
cout<<a2<<endl;//ok
cout<<a3<<endl;//ok