ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

第04章_复合类型

2022-07-10 11:00:22  阅读:127  来源: 互联网

标签:存储 04 int 复合 数组 类型 new delete 指针


<c++ primer plus>第六版

目录

4 复合类型

4.1 数组

声明数组的格式:
typeName arrayName[arraySize]; //声明一个数组, 内容未定;
typeName arrayName[arraySize] = {20, 30}; //声明一个数组, 并初始化, 初始化的数目可以小于等于arraySize, 未声明的元素初始化为0.
typeName arrayName[] = {1, 2, 3}; //让编译器自动计算数组大小.

//ok with c++11
double a0[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; //省略等号
double a1[4] {}; //大括号内为空, 所有元素初始化为0

其中arraySize, 只能是整型常数, const值, 或常量表达式, 不能是变量(程序运行时设置的值). 使用new运算符可以避开些限制.

例:
float loans[20];

数组长度: sizeof(arrayName)/sizeof(typeName)

4.2 字符串

C-风格字符串:
char dog[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string, 必须以空字符'\0'结尾.
char fish[] = "Bubbles"; //字符串常量, 隐式地包括结尾的空字符'\0'.

拼接字符串常量
cout << "A colorful" " day.\n"; // 由空白分隔的两个字符串合并, 拼接时不会添加空格.
cout << "A colorful"
" day.\n" // 换行符也可以作为拼接空白符.

#include <iostream>
#include <cstring> //包含strlen()函数
int main(){
    using namespace std;

    char name0[] = "HanMeimei"; //name0包含9个字面字符
    char name1[5];

    cout << "name0 is " << name0 << endl; //字符串常量可以直接写在cout中.
    cout << "name0 length is " << strlen(name0) << endl; //9, 获取字符串长度, 字面长度, 不包括结尾的'\0'.
    cout << "name0 size is   " << sizeof(name0) << endl; //10, 获取字符串字节数, 包括结尾的'\0'.
    name0[3] = '\0'; // 字符串中间设置为'\0', 将强行截取字符串
    cout << "name0 is " << name0 << endl; // Han, 之后的部分被截掉了.
    cout << "name0 length is " << strlen(name0) << endl; //3, 获取字符串长度, 看到'\0'即止.
    cout << "name0 size is   " << sizeof(name0) << endl; //10, 数组仍然占10bit.

    cout << "Enter name1: ";
    cin >> name1; //从cin读入到字符串常量.
    cout << "name1 is " << name1 << endl;

}

按行读入
cin.getline(name, 20) // 把一行读入到name数组中(需要这行不超过19个字符).

4.3 string类

存储字符串, 不再使用字符数组, 而是使用string类的对象, string类使用比较简单, 隐藏了字符串的数组性质.

string str1 = "panther";
string str2 = "123";

str2 = str1;

#include <iostream>
#include <string>
int main(){
    using namespace std;
    string str1 = "Hello";
    string str2 = " World.";
    string str3 = "tmp";
    str3 = str1 + str2; //允许使用+来连接字符串, 允许把一个字符串赋值给另一个字符串.

    cout << str1 << str2 << endl;
    cout << str3 << endl;
    cout << str3.size() << endl; //获取string的长度
}

wchar_t title[] = L"Chief Astrogator"; // w_char string
char16_t name[] = u"Felonia Ripova"; // char_16 string
char32_t car[] = U"Humber Super Snipe"; // char_32 string

4.4 结构体

struct inflatable{
char name[20];
float volume;
double price;
};

结构体数组
inflatable gifts[100]; // gifts本身是一个数组.

4.6 枚举

enum spectrum {red, orange, yellow, blue, violet, indigo, ultraviolet};
spectrum band; //枚举变量
band = blue; //对变量赋值
cout << blue << endl; // 3, blue对应的值是3

4.7 指针和自由存储空间

计算机存储数据时要跟踪3个属性:

  1. 存储地址
  2. 存储值
  3. 数据类型

&: 地址运算符, 在变量前加上&可以获得它的地址;
*: 解除引用运算符, 将其应用于指针, 可以得到该指针指向的地址处存储的值.

声明指针: type * var;

*两边的空格是可选的
int ptr; //强调 ptr是个int类型的指针;
int
ptr; //强调 int
是一种类型: 指向int的指针.
int * p1, p2; //p1是指针, p2是int变量. 所以感觉还是 int *p1, p2比较符合直观.

初始化指针:
int higgens = 5;
int* pt = &higgens; //将指针pt初始化为higgens的地址, 而不是给*pt(pt指向的内容)赋值.

long* fellow;
*fellow = 223322; //危险, fellow是个指针, 但不知道它指向哪里.

pt = 0xB8000000; //类型错误, 计算机并不知道0xB8000000是个地址.
pt = (int *) 0xB8000000; //正确, 告诉编译器0xB8000000是个地址.

4.7.4 使用new分配内存

指针可以在运行阶段分配未命名的内存以存储值:
typeName * pointer_name = new typeName;
int* pn = new int; //为一个int值分配未命名的地址. new运算符是c++的运算符.

//内存块的差别
new: 从堆(heap)或自由存储区(free store)的内存区域分配内存.
常规变量声明: 从栈(stack)的内存区域分配内存.

4.7.5 使用delete释放内存

int* ps = new int;
delete ps; //delete后要加上指针. 释放ps指向的内存, 但不会删除指针ps本身, ps可以再指向新分配的内存.

注意:

  1. 要配对使用new和delete, 否则将发生内存泄漏(memory leak);
  2. 不能释放已经释放的内存块, 这样结果不确定;
  3. 不能用delete来释放声明变量所获得的内存;

int *ps = new int; // ok
delete ps ; // ok
delete ps ; // not ok now

int jugs = 5 ; // ok
int *pi = &jugs ; // ok
delete pi ; // not allowed, memory not allocated by new.

4.7.6 使用new来创建动态数组

通过声明创建数组, 在程序编译时就会为他分配内存空间, 不管最终是否使用数组, 它都会占用内存. 称为静态联编.
通过new创建数组, 则在编译时不分配内存空间, 运行时, 如果需要数组则创建, 如果不需要数组则不创建, 且可以在程序运行时选择数组长度. 称为动态联编. 这种数组称为动态数组.
int * psome = new int[10]; //创建一个指针, 指向第一个元素的地址.
delete [] psome; //需要使用delete释放, 方括号表示要释放整个数组, 而不仅仅是指针指向的元素.

创建动态数组的语法:
type_name * pointer_name = new type_name [num_element];
delete [] pointer_name;

使用动态数组:
int * psome = new int [10]; //创建

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

    double * p3 = new double[3];
    p3[0] = 0.2;
    p3[1] = 0.5;
    p3[2] = 0.8;
    cout << "p3[0]=" << p3[0] << endl;
    cout << "p3[1]=" << p3[1] << endl;

    p3 += 1; //指针增加(对数组名不能做加法), 增加量为它指向类型的字节数.
    cout << "p3[0]=" << p3[0] << endl; //指向原本的[1]
    cout << "p3[1]=" << p3[1] << endl; //指向原本的[2]

    delete [] p3; //释放内存
    return 0;
}
#include<iostream>
int main(){
    using namespace std;

    double wages[3] = {10.0, 20.0, 30.0};
    short stacks[3] = {3, 2, 1};

    double * pw = wages;     //指向数组的指针, 数组名解释为数组第一个元素的地址(wages=&wages[0]).
    short * ps = &stacks[0]; //数组的第0元素的地址, 也是指向数组的指针.

    cout << " pw=" <<  pw << endl;
    cout << "*pw=" << *pw << endl;
    cout << " ps=" <<  ps << endl;
    cout << "*ps=" << *ps << endl;

    pw += 1;
    ps += 1;
    cout << " pw=" << pw << endl;
    cout << "*pw=" << *pw << endl;
    cout << " ps=" <<  ps << endl;
    cout << "*ps=" << *ps << endl;

    cout << "stacks[0]=" << stacks[0] << endl;
    cout << "stacks[1]=" << stacks[1] << endl;

    cout << "*stacks  =" << *stacks << endl;
    cout << "*(stacks+1)=" << *(stacks+1) << endl; //等价于stacks[1], c++会将stacks[1]转换为*(stacks+1)

    cout << "sizeof(wages)=" << sizeof(wages) << endl; //数组的长度(字节数)
    cout << "sizeof(pw   )=" << sizeof(pw) << endl; //指针的长度.
}

//声明指针
double * pn;
char   * pc;

//给指针赋值
double * pn;
double * pa;
char   * pc;

double bubble = 3.2;
pn = &bubble; //将地址赋值给指针.
pc = new char; //把一个通过new分配的char的地址赋值给pc.
pa = new double[30]; //30个元素的array, 把第一个地址赋值给pa.

//对指针解除引用(获取其指向的值)
cout << *pn << endl; //打印指针指向的值
*pc = 'S'; //更改指针指向内存的内容

//数组名
int tacos[10]; // 数组名即第一个元素的地址: &tacos[0], 只有一个例外, 见下一行.
sizeof(tacos); // sizeof(数组名), 返回的是整个数组的长度, 而不是第一个元素的长度.

//指针算术(加, 减)
int tacos[10] = {5,2,8,4,1,2,2,4,6,8};
int * pt = tacos;
pt = pt + 1;
int * pe = &tacos[9];
int diff = pe - pt;

//数组的动态联编和动态联编
int tacos[10]; //通过声明来创建的数组, 采用静态联编, 数组长度在编译时设置;
int size;
int * pz = new int[size]; //通过new创建数组, 采用动态联编, size可以在运行时设置
...
delete [] pz; //使用new创建的数组需要delete.

//数组表示法和指针表示法
//方括号数组表示法等同于对指针解除引用.
tacos[0]; // *tacos
tacos[3]; // *(tacos+3);

int * pt = new int[10];
*pt = 5 // 第0个元素设置为5
pt[0] = 6; //第0个元素设置为6
pt[9] = 44; //第9个元素设置为6

int coats[10];
*(coats+4) = 12 // 设置costs[4]为12;

//指针与字符串

4.8.4 使用new创建动态结构

#include<iostream>

struct inflatable
{
    char name[20];
    float volume;
    double price;
};

int main(){
    using namespace std;
    inflatable *ps = new inflatable; //分配内存

    cout << "Enter name of inflatable: ";
    cin.get(ps->name, 20);

    cout << "Enter volume            : ";
    cin >> (*ps).volume;

    cout << "Enter price             : ";
    cin >> ps -> price;

    cout << endl;
    cout << "Name  :" << (*ps).name << endl;   // 使用 (*指针).成员 的方式访问
    cout << "Volume:" << ps -> volume << endl; // 使用 指针->成员 的方式访问
    cout << "Price :" << ps -> price << endl;

    delete ps;
    return 0;
}

4.8.5 自动存储, 静态存储, 动态存储

  1. 自动存储(栈):
    在函数内部定义的常规变量使用自动存储空间, 称为自动变量, 函数结束时消亡.
    如果函数需要返回复合类型时, 需要使用new创建的指针, 否则函数结束后返回值也没了.
    自动变量是局部变量, 作用域为包含它的代码块.
    自动变量通常存储在栈中. 先进后出LIFO. 程序执行过程中栈将不断变大和缩小.

  2. 静态存储:
    在整个程序执行期间都存在的存储方式.
    变量成为静态有两种方式: 1) 在函数外定义它; 2) 声明变量时使用static;
    与自动存储的区别主要在于变量的寿命.

  3. 动态存储(自由存储区/堆):
    new和delete运算符提供了比自动变量和动态变量更灵活的方法.
    new和delete管理了一个内存池, 在c++中称为自由存储空间(free store)或堆(heap).
    new和delete允许在一个函数中分配内存, 而在另一个函数中释放它. 从而数据的生命周期不完全受程序或函数的寿命控制.
    new和delete让程序员对程序如何使用内存有更大的控制权(内存管理也更复杂了).
    new和delete相互作用, 使栈中的内存占用不连续.

4.9 类型组合

struct antarctical_years_end
{
    int year;
}
antarctical_years_end s01, s02, s03;

s01.year = 1998; //使用成员运算符

antarctical_years_end * pa = &s2; //指针
pa->year = 1999; //使用间接成员运算符

antarctical_years_end trio[3]; //结构数组
trio[0].year = 2003; //trio[0]是结构, 而不是指针.

(trio+1)->year = 2004; //数组名是指针, 等价于trio[1].year

const antarctical_years_end * arp[3] = {&s01, &s02, &s03};//指针数组, 元素都是指针
cout << arp[1]->year; //arp[1]是个指针, 所以使用间接成员运算符.

const antarctical_years_end **ppa = arp; // 指向上述数组的指针, 写法易出错;
auto ppb = arp; //使用automatic type deduction

cout << (*ppa)->year; //ppa是指向结构指针的指针, *ppa是一个结构指针, 可以使用间接成员运算符.
cout << (*(ppb+1))->year;

4.10 数组的替代品

模板类vector和array是数组的替代品;

4.10.1 模板类vector

vector类似于string类, 也是一种动态数组.
可以在运行阶段设置vector对象长度, 可以在末尾附加新数据, 可以在中间插入新数据.
vector类使用new和delete来处理内存, 但这种工作是自动完成的.

语法:

#include<vector>
using namespace std;
vector<typeName> vt(n_elem); //n_elem可以是整型常量, 也可以是整型变量.

4.10.2 模板类array(c++11)

vector类功能比array强大, 但效率较低.
array的长度(与数组一样)是固定的, 使用栈(静态内存分配), 不在自由存储区.
array与数组的效率相同, 差别是array更方便, 更安全.

语法:

#include<array>
using namespace std;
array<typeName, n_ele> arr; //n_elem不能是变量.

4.10.3 比较: 数组, vector对象, array对象

  1. 都可以使用标准数组的表示法来访问: arr[idx];
  2. 数组和array对象存储在栈中, vector对象存储在堆(自由存储区)中;
  3. 可以把一个array赋值给另一个array, 但数组必须逐元素复制.
  4. 三者索引号都可以越界, 比如a1[-2], 表示*(a1-2), 会将地址指到数组外面, 但c++不检查这种越界错误.
  5. vector和array对象解决越界方法是, 使用成员函数at(), a1.at(1), 程序运行时会捕获非法索引, 从而默认将程序中断, 代价是运行时间长.
  6. vector和array对象还包含成员函数begin()和end(), 用于确定边界, 以免无意间越界.

4.11 总结

  1. 数组, 结构, 指针, 是c++的3种复合类型.
  2. 数组: 在一个数据对象中存储多个同种类型的值, 使用索引或下标可以访问数组各元素.
  3. 结构: 在一个数据对象中存储多个不同类型的值, 使用成员关系运算符(.)访问成员.
  4. 共用体: 可以存储一个值, 但这个值可以是不同类型.
  5. 指针: 一个变量, 用来存储地址. 对指针使用"解除引用运算符", 将得到指向的位置中的值.
  6. 字符串: 以空字符结尾的一系列字符. string支持使用赋值运算符来复制字符串(char数组需要使用strcpy())
  7. new运算符: 程序运行时为数据对象请求内存, new返回获得的内存地址, 可以将地址赋值给一个指针.
  8. 模板类vector是动态数组的替代品.
  9. 模板类array是定长数组的替代品.

标签:存储,04,int,复合,数组,类型,new,delete,指针
来源: https://www.cnblogs.com/gaiqingfeng/p/16462763.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有