【捡起C++】复合类型

​ 结构可以存储多个不同类型的值,而指针则是一种将数据所处位置告诉计算机的变量。

字符串

​ C-风格字符串具有一种特殊的性质: 以空字符(null character)结尾,空字符被写作\0,其ASCII码为0,用标记字符串结尾。

char dog[8] = {'b', 'e', 'a', 'u', 'x', '', 'I', "I"};   // not a string
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'};  // a string

​ 这两个数组都是char数组,但只有第二个数组是 字符串

​ 字符串常量(使用双引号) 不能与 字符常量 (使用单引号)互换。

char shirt_size = ‘s’; // this is fine

​ 但 "S"不是字符常量,他表示的是两个字符组成的字符串。而且,"S"实际表示的是字符串所在的内存地址。

char shirt_size = “S”; // this is illeagal;

​ 地址在c++中是一种独立的类型,c++编译器不允许这种不合理的做法。

​ 拼接字符串时,第一个字符串中的\0字符将被第二个字符串的第一个字符取代。

cin.getline() 和 cin.get()的区别

​ 这两个函数都读取一行输入,直到到达换行符。然而,随后getline() 将丢弃换行符,而get()将换行符保留在输入序列中。

cin.get(name, Arsize);
cin.get(dessert, Arsize);         //a problem

由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。

int year;
char address[80];
cin >> year;
cin.getline(address, 80);

​ 当 cin 读取年份,将回车键生成的换行符留在了输入队列中。后面的cin.getline()看到换行符后,将认为是一个空行,并将一个空字符串赋值给address。

​ C++ 常使用指针来处理字符串。

String类简介

​ 使用string类必须加头文件 #include < string >

​ 与使用字符数组比,string更安全,更方便。

​ string类简化了字符串合并的操作,使用运算符 “+” 将两个string对象合并起来。

String 类 I/O

每次读取一行而不是一个单词时,其句法与处理C 风格字符串不同。

char charr[20];
string str;
cin.getline(charr, 20);  
getline(cin, str);

cin.getline(charr, 20); 第一个参数是目标数组,第二个参数是参数数组长度,getline()使用它来避免超越数组的边界。

getline(cin, str); 这里没有使用句点表示法,表明这个getline()不是类方法。它将cin 作为参数,指出去查找输入。 也没有指出字符串长度,因为string对象可以自动调整长度。

istream中有考虑诸如double 、int等基本类型,但没有考虑string类型,istream类中没有处理string对象的类方法。

其他形式的字符串字面值
wchar_t title[] = L"Chief Astrogator";
char16_t name[] = u"Felonia Ripova";
char32_t car[] = U"Humber Super Snipe";
结构简介
#include<iostream>
struct inflatable
{
	char name[20];
	float volume;
	double price;
};
int main()
{
	inflatable guest = {
		"Glorious",
		1.88f,
		29.99
	};
    std::cout << R"(Hello World!\n)"; 
}

与数组一样,C++11也支持将列表初始化用于结构,且等号是可选的。

inflatable duck {"Daphne", 0.12, 9.98};

如果大括号内不包含任何东西,各个成员 都将被设置为零。

最后, 不允许缩窄转换

C++使用户定义的类型与内置类型尽可能相似。例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,可以使用赋值运算符 = 将结构赋给 另一个同类型的结构。

#include<iostream>
struct inflatable
{
	char name[20];
	float volume;
	double price;
};
int main()
{
	inflatable guest = {
		"Glorious",
		1.88f,
		29.99
	};
    inflatable choice;
    choice = guest;
    std::cout << R"(Hello World!\n)"; 
}
struct perks{
    int key_number;
    char car[12];
}mr_smith, ms_jones;         //同时完成定义结构和创建结构变量



//这也是种初始化的方式
struct perks{
    int key_number;
    char car[12];
}mr_glitz = {
    7,
    "Packard"
};

//省略结构类型
struct{
    int x;
    int y;
}position;

C++结构除了成员变量之外,还可以有成员函数。

#include<iostream>
struct inflatable
{
	char name[20];
	float volume;
	double price;
};
int main()
{
	inflatable guest[2] = {
        {"Glorious", 1.88f, 29.99},
        {"Godzilla", 2000, 565.99}
	}; 
}
结构中的位字段

​ 与C语言一样,C++也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。

字段的类型应为整型或枚举,接下来是冒号,冒号后面是一个数字,它指定了使用的位数。

可以使用没有名称的字段来提供间距,每个成员都被称为位字段(bit field)。

struct torgle_register{
    unsigned int SN : 4;    //4 bits for SN value
    unsigned int : 4;       //4 bits unused
    bool goodIn : 1;        //valid input (1 bit)
    bool goodTorgle : 1;    //successful torgling
}

​ 位字段通常用在偏底层的编程中。

共用体

​ 共用体能存储不同的数据类型,但只能同时存储其中的一种类型。

union one4all{
    int int_val;
    long long_val;
    double double_val;
};
one4all pail;

pail 有时可以是int 变量,有时又可以是double 变量。共用体的长度为其最大成员的长度。

枚举

​ C++的enum提供了另一种创建符号常量的方式,这种方式可以代替const。

enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
  • 让spectrum成为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被成为结构一样。
  • 将red、orange、yellow等作为符号常量,它们对应的整数值 0 ~ 7。这些常量叫做枚举量。
设置枚举量的值

可以使用赋值运算符来显式地设置枚举量的值:

enum bits{one = 1, two = 2, four = 4, eight = 8};

指定的值必须是整数

enum bigstep{first, second = 100, third};

first 默认情况下为0,后面没有被初始化的枚举量的值将比其前面的枚举量大 1。 third = 101.

可以创建多个值相同的枚举量

enum {zero, null = 0, one, numero_uno = 1};

其中,zero和null都为0,one和numero_uno都为1。

枚举的取值范围

​ 假设bits 和 myflag的定义如下:

enum bits{one = 1, two = 2, four = 4, eight = 8};
bits myflag;
myflag = bits(6);   // valid, because 6 is in bits range

6 不是 枚举值,但它位于枚举定义的范围内。

上限:大于最大值的、最小的2的幂,将其减一。比如bits中,最大值是8,大于8的最小的2的幂是16, 16 - 1 = 15, 上限是 15;

下限:枚举量的最小值如果小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式,但加上负号。

指针和自由存储空间

​ 一种特殊类型的变量——指针用于存储值的地址。

​ 指针名表示的是地址,***** 运算符被称为间接值(indirect value )或 解除引用(dereferencing)运算符,将其应用于指针,可以得到该地址处的存储的值。

​ char的地址和double的地址看上去没什么两样,但char和double使用字节数是不同的,存储时使用的内部格式也不同。因此, 指针声明必须指定指针指向的数据的类型。

int* p_updates;

这表明,*p_updates的类型为int。 我们说 p_updates指向int类型,还说 p_updates的类型时指向int的指针,或 int *。可以这样说,p_updates是指针,而 *p_updates是int,而不是指针。

double *tax_ptr;
char *str;

​ 虽然tax_ptr 和 str指向两种长度不同的数据类型,但这两个变量本身的长度通常都是相同的。 也就是说,char 的地址与double 的地址的长度相同。

int main()
{
	int higgens = 5;
	int* pt = &higgens;
	cout << higgens << "   " << &higgens << endl;
	cout << *pt << "   " << pt;
	return 0;
}

指针的危险

​ 在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。

​ 为数据提供空间是一个独立的步骤,忽略这一步是自找麻烦。

long* fellow;      //create a pointer-to-long
*fellow = 223333;  //place a value in never-never land
指针和数字

​ 指针不是整型,虽然计算机通常把地址当做整数来处理。

​ 要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:

int *pt;
pt = (int*) 0xB8000000; //types now match

​ 这样,赋值语句两边都是整数的地址,因此这样赋值有效。 **注意,**pt是 int值的地址并不意味着pt本身的类型时int。有些平台下,int类型是2字节,地址是4个字节。

使用 new 来分配内存

​ 指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。

​ 在运行阶段为一个 int 值分配未命名的内存,并使用指针来访问这个值。

​ new 将找到一个长度正确的内存块,并返回该内存块的地址。

int * pn = new int;
int nights = 1001;

​ 对于指针, new 分配的内存块通常与常规变量声明分配的内存块不同。 变量nights 的值 存储在 栈区,而 new 从被称为 自由存储区 的内存区域分配内存。

使用delete释放内存
int * ps = new int;
delete ps;

​ 这会释放ps指向的内存,但不会删除指针ps本身。可以将 ps 重新指向另一个新分配的内存块。

一定要配对使用 new 和 delete,否则会发生内存泄漏

使用new来创建动态数组

​ 如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序是否使用数组,数组都在那里,它占用了内存。 在编译时给数组分配内存被称为 static binding,但使用new 时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时指定数组的长度。这种数组被称作 dynamic array

int * psome = new int[10];

​ new 返回第一个元素的地址。

delete [] psome;
指针、数组和指针算术

​ 指针变量加 1,增加的量等于它指向的类型的字节数。将指向double的指针加 1 后,如果系统对double使用8个字节存储,则数值将增加 8。

多数情况下,c++将数组名解释为数组第一个元素的地址。

double wages[3] = {1.0,2.0,3.0};
double * pw = wages;

​ 和所有数组一样,wages也存在下面的等式。

wages = &wages[0] = address of first element of array

​ 对数组取地址时,数组名也不会被解释成其地址。数组名被解释成其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址。

short tell[10]; 
cout << tell << endl;    //displays &tell[0]
cout << &tell << endl;   //display address of whole array

​ 从数字上说,这两个地址是相同的;但从概念上说,&tell[0] (即tell) 是一个2字节内存块的地址,而 &tell 是一个20字节内存块的地址。表达式 tell + 1将地址值加2, 表达式 &tell + 2将地址加20。 tell 是一个 short 指针(*short)。而&tell是指向包含20个元素的short数组 (short ( * ) [20]);

数组指针(也称行指针)

定义 int (*p)[n];

()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

如要将二维数组赋给一指针,应这样赋值:

int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

指针数组
定义 int p[n];
[]优先级高,先与p结合成为一个数组,再由int
说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里 *p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:

int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for(i=0;i<3;i++)
p[i]=a[i]

​ 这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]所以要分别赋值。

​ 这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:

*(p[i]+j)*(*(p+i)+j)(*(p+i))[j]、p[i][j]
优先级:()>[]>*
指针和字符串

​ 在c++中,用引号括起的字符串像数组名一样,也是第一个元素的地址。

const char * bird = "wren"; // bird holds address of string

​ "wren"实际表示的是字符串的地址,因此这条语句将"wren"的地址赋给了bird指针。

​ 如果给cout提供一个指针,它将打印地址;但如果指针的类型是char *,则cout将显示指向的字符串。如果要显示字符串的地址,则必须强制转换成另一种指针类型,如 int *。

​ 要获得字符串的副本,还需要分配内存来存储该字符串,这可以通过声明另一个数组或使用new来完成。后一种方法使得能够根据字符串的长度来指定所需的空间。

char animal[20] = "bear";
char* ps = new char[strlen(animal) + 1];

​ 直接将animal赋给ps是不行的,因为这样只能修改存储在ps中的地址,从而失去程序访问新分配内存的唯一途径。

strcpy(ps, animal); // copy string to new storage

​ strcpy 会有数组越界的情况,strncpy可以设置复制的最大字符数。

使用new创建动态结构
inflatable *ps = new inflatable;

​ 如果ps指向一个inflatable结构,则ps -> price 是被指向的结构的price 成员。

​ 另一种访问方式是,(*ps).price;

自动存储、静态存储和动态存储


#include<iostream>
#include<cstring>
using namespace std;
char* getname(void);
int main() {
	char* name;
	name = getname();   // assign address of string to name
	cout << name << " at " << (int*)name << endl;
	delete[] name;      // memory freed

	name = getname();   // reused free memory
	cout << name << " at " << (int*)name << endl;
	delete[] name;
	return 0;
}

char* getname() {
	char temp[80];
	cin >> temp;
	char* pn = new char[strlen(temp) + 1];
	strcpy(pn, temp);
	return pn;
}
自动存储

​ 函数内部定义的常规变量使用自动存储空间,被称为自动变量。它们在函数调用时自动产生,在函数结束时消亡。

​ 自动变量通常存储在栈中,这意味着执行代码块时,其中的变量将依次加入栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出。

​ 因此,在程序执行过程中,栈将不断地增大和缩小。

静态存储

​ 静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明时使用static关键字。

static double fee = 56.50;
动态存储

​ new 和 delete运算符提供了一种更灵活的方法。他们管理了一个内存池,这在c++中被称为自由存储空间 或 堆。

​ new 和 delete的相互影响可能导致 堆区 不连续,这使得跟踪新分配内存的位置更困难。

数组的替代品
模板类vector

​ vector 类似于 string,也是一种动态数组。可在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。

​ vector 是使用new 和 delete来管理内存的,但这种工作是自动完成的。

#include <vector>
using namespace std;
int n;
cin >> n;
vector<double> vd(n);
模板类array (c++11)

​ vector类的功能比数组强大,但付出的代价是效率稍低。

​ C++11 新增了模板类array,与数组一样array对象的长度是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便安全。

#include <array>
using namespace std;
array<int, 5> ai;
array<double, 4> ad = {1.2, 2.1, 3.43, 4.3};

array<typename, n_elem> arr

与vector不同的是,n_elem不能是变量。

array 对象 和 数组 储在 栈区, vector对象存储在 堆区

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值