1、数组
数组声明应该指出元素类型、数组名和元素个数。数组的初始化需要遵循以下规则:
a. 只有定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组
int narray1[4] = { 1, 2, 3, 4 };
int narray2[4];
// narray2 = { 2, 3, 4, 5 }; // not allowed
// narray2 = narray1; // not allowed
初始化结束后只能通过下标来给数组元素赋值
b. 初始化数组时,提供的值可以少于数组的元素数目
int narray1[4] = { 1, 2};
c. 初始化数组时没有指定元素个数,C++编译器将计算元素的个数,我们可以通过sizeof来计算元素个数
int arr[] = { 1, 2, 3, 4};
cout << sizeof(narray1) / sizeof(narray1[1]);
数组的初始化方法:
列表初始化(花括号初始化)作为通用初始化方式,适用于任何类型,当然也支持数组初始化。
int arr1[3] = { 1, 2, 3 };
int arr2[]{ 1, 2, 3, 4 };
int arr3[10]{};
花括号里不填任何东西时,所有元素将被设置为0。
2、字符串
字符串是存储在连续内存中的一串字符。C++处理字符串的方式有两种:C风格字符串 和 基于string类的字符串。
a. C风格字符串
C风格字符串以空字符'\0'结尾,ASCII值为0:
char str1[] = { 'h', 'e', 'l', 'l', 'o' }; // not a string
char str2[] = { 'h', 'e', 'l', 'l', 'o', '\0'}; // string
除了用数组来初始化字符串,还有一种双引号括号的字符串,这种字符串被称为字符串常量:
char str1[] = "this is a const string";
str1[0] = 'a';
cout << str1; // ahis is a const string
我们可以用数组下标来修改字符串的内容。
任何两个有空白(空格、制表符、换行符)分隔的字符串常量可以拼接为一个,拼接后的字符串之间不会添加空格,前一个字符串的'\0'会被后一个字串取代
cout << "hello " "world " "!!!!" << endl; // hello world !!!!
cout << "hello "
"world "
"!!!!" << endl; // hello world !!!!
#define HEL "hello"
cout << HEL" world"; // hello world
char str[] = "hello " "world"
"!!!";
cout << str; // hello world!!!
b. string类简介
string类位于命名空间std中,可以通过using std::string来引入,string类隐藏了字符串的数组性质。string可以有以下初始化方式:
string str1 = "hello world";
string str2 = { "hello world" };
string str3("hello world");
string str4{ "hello world" };
string str5 = str1;
string str6(str1);
3、结构体简介
结构体是一种比数组更为灵活的数据格式,同一个结构可以存储多种类型的数据。结构体可以有外部声明(可以被其后面的任何函数使用)和内部声明(被该声明所属的函数使用)。
C++11结构创建与初始化:
struct student
{
char name[20];
int age;
bool gender;
};
student s1 = { "ming", 11, true };
student s2{ "hong", 10, false };
student s3{}; // 初始化为0
student s4; // 这时候结构体中的成员都是未初始化的,使用可能会出现未知的问题
memset(&s4, 0, sizeof(s4));
// 指定初始化,不是所有的编译器都支持的,linux中常用
student s5 = { .name = "bing", .age = 10, .gender = false };
结构体定义和创建结构体变量可以同时完成,初始化也可以同时完成:
struct student
{
char name[20];
int age;
bool gender;
} s1 = {}, s2 =
{
"ming", 11, true
};
我们还可以声明匿名的结构体类型,但是我们必须要在定义的同时声明变量:
struct
{
int x;
int y;
} position;
有了结构体我们可以声明结构体数组了:
student ss[3]{
{ "ming", 11, true },
{ "hong", 10, false },
{ }
}
结构体中的位字段:与C语言一样,C++也允许指定占用特定位数的结构成员,使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整形或者枚举,接下来是冒号,冒号后面是一个数字,指定了使用的位数。可以使用没有名称的字段提供间距。每个成员都被称为位字段。
struct torgle_register
{
unsigned int a : 4; // 4 bits for SN
unsigned int : 4; // 4 bits unused
unsigned int b : 2; // 2 bits unused
unsigned int c : 14; // 14 bits unused
} tr;
tr = { 0xf, 0x3, 0xfff };
cout << hex;
cout << tr.a << " " << tr.b << " " << tr.c << endl; // f 3 fff
tr = { 0xf1, 0x5, 0x4fff };
cout << tr.a << " " << tr.b << " " << tr.c << endl; // 1 1 fff
cout << "sizeof(tr) = " << sizeof(tr); // 4
位段的存储规则:当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。计算位段结构体大小时可能还需要兼顾到内存对齐,这里暂不介绍。
4、共用体
共用体能够存储不同的数据类型,但是只能同时存储其中的一种类型。由于每次只能存储一个值,所以必须要有足够的空间来存储最大的成员,共用体的长度为其最大成员的长度。
struct widget {
char brand[20];
int type;
union id {
long id_num;
char id_char[20];
} id_val;
};
cout << sizeof(widget); // 44
w1.id_val.id_num = 10;
w1.id_val.id_char[0] = 20;
cout << w1.id_val.id_num; // 20
匿名共用体的成员会被是为结构体成员的两个成员,我们使用时需要先确定哪个成员是正在活动的:
struct widget2 {
char brand[20];
int type;
union{
long id_num;
char id_char[20];
};
} w1 = {};
if (w1.type == 1)
w1.id_num = 10;
else
memcpy(w1.id_char, "apple", strlen("apple"));
5、枚举
C++的enum提供了另一种创建符号常量的方式,这种方式可以替代const。
默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,以此类推;此外,我们可以通过显式指定整数值来覆盖默认值,我们也可以创建多个值相同的枚举量。
enum MyEnum
{
APPLE,
ORANGE,
BANANA,
};
枚举变量有些特殊的属性:在不进行强制转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量。枚举量是整形,可被提升为int类型,但是int类型不能自动转换为枚举类型。
MyEnum fruit;
fruit = APPLE;
// fruit = 1000; // invalid
fruit = MyEnum(2);
// fruit = 1 + APPLE; // invalid
// fruit = 2; // invalid
int nu = 1 + APPLE; // valid
枚举的上限为大于最大值的、最小的2的幂减1,最小值如果大于等于0则下限为0,否则下限的计算方式和上限相同。下例中6不是枚举值,但是它位于枚举定义的取值范围内。
enum bits {
one = 1,
two = 2,
four = 4,
eight = 8,
};
bits myflag = bits(6);
// bits myflags2 = bits(9); // invalid
6、指针和自由存储空间
指针是一个变量,存储的是一个值的地址,而不是值本身。指针声明必须指定指针指向的数据的类型。以下声明的是一个指针变量和一个int变量:
int * p, p1;
我们可以通过new 和 delete来分配、释放内存
int * p = new int;
*p = 100;
delete p;
p = NULL;
也可以通过malloc 和 free来分配、释放内存
int *p = (int*)malloc(sizeof(int));
*p = 100;
cout << p << " " << *p << endl; // 00DE89B8 100
free(p);
p = NULL;
使用new来创建动态数组:
int *p = new int[10];
// int p[10] = new int[10]; // invalid
delete[] p;
7、指针、数组和指针算数
指针变量加1,其增加的值等于指向的类型占用的字节数。
很多情况下我们可以以相同的方式使用指针名和数组名,包括方括号表示法和解引用运算符。区别是指针的值可以被修改,而数组名是常量;另一个区别是对数组应用sizeof运算符得到的是数组的长度,对指针应用sizeof得到的是指针的长度。
int array[10]{};
int *parray = new int[10];
array[2] = 2;
*(array + 3) = 3;
parray[2] = 2;
*(parray + 3) = 3;
parray++;
// array = array + 1; not allowed
cout << sizeof(array) << endl; // 40
cout << sizeof(parray) << endl; // 4
int *p = array;
数组名会被解释为其第一个元素的地址,对数组名应用地址运算符时,得到的是整个数组的地址
short tell[3]{};
cout << &tell[0] << endl; // 012FFB64
cout << tell << endl; // 012FFB64
cout << &tell << endl; // 012FFB64
cout << tell + 1 << endl; // 012FFB66 + 2
cout << &tell + 1 << endl; // 012FFB6A + 6
数组指针:指向数组的指针
变量首先和*结合说明是一个指针,指向的类型是 short []
short (*pas)[3] = &tell;
指针数组:存储指针的数组
变量首先和[]结合说明是一个数组,存储的类型是short *
short *pp[3]{};
指向同一个数组的指针相减得到的是两个元素之间的间隔
short *p1 = tell;
short *p2 = &tell[2];
cout << p2 - p1 << endl; // 2
cout << p1 - p2 << endl; // -2
8、自动存储、静态存储和动态存储
自动存储:函数内部定义的常规变量使用自动存储空间被称为自动变量,自动变量是一个局部变量,其作用域为包含它的代码块。
{
int a = 0;
}
静态存储:整个程序执行期间都存在的存储方式,变量成为静态的方式有两种:一种是在函数外面定义它,另一种是在声明变量时使用关键字static。
// 函数外部明静态变量
static int ga = 0;
int addA()
{
ga++;
return ga;
}
int a = addA();
cout << a << endl; // 1
a = addA();
cout << a << endl; // 2
// 函数内声明静态变量
int addA()
{
static int a = 0;
a++;
return a;
}
int a = addA();
cout << a << endl; // 1
addA();
cout << a << endl; // 2
以上两种声明方式的不同点在于:在函数内部声明的静态变量只能在函数内部访问到,而外部声明在声明位置之后都可以访问到。
动态存储:new和delete运算符提供了一种更灵活的方法,他们管理了一个内存池(heap),数据的生命周期不完全受程序或函数的生存时间控制。