您的位置:首页 > 编程语言 > C语言/C++

第四章 复合类型 C++ Primer Plus 2018_3_10

2018-03-10 18:15 381 查看
博主精要总结:




第四章
一 本章内容包括:                                                                                                                                                                  

引言

数组
结构体
指针
字符串

共用体
枚举

 new delete 管理内容

动态数组
动态结构
类模板vector类 简介
类模板array类 简介

二 知识点:                                                                                                                                                                

1 引言

博主在学习C
4000
++ Primer Plus 第三章 处理数据 时了解到:
“内置C++类型分两种:基本和复合类型

   基本类型包括整数、浮点数;复合类型包括数组、字符串、指针、结构等......”(博文地址---点击打开链接
基本类型已经介绍过,对于复合类型而言,实际上她是由基本整型和浮点类型创建的。
数组---存储多个同类型的值或者存储字符串
结构---存储多个不同类型的值

指针---存储地址,将数据所处的位置告诉计算机的变量
对于复合类型,其影响最为深远的是类。
同时,根据博主的项目经验,本文讨论了string类中的一些函数,如数据包传输的时候,发送方如何让多个字符串打包发送,接收方又如何对数据包进行解析等等。

除此之外,本文还将重点讨论new和delete以及她们对数据的管理。

2 数组

(1)如何使用
    定义:“数组---存储多个同类型的值或者存储字符串”
    声明注意点:
数组名 --- 地址常量 常与 指针变量 联用

元素类型 --- 数组中所有元素类型一致

元素个数 --- 每个元素可视为一个简单变量
    声明通用格式: typeName arrayName[arraySize];
    概念模糊:double array[10];//错误:这是一个数组 正确:这是一个double类型的数组
    使用:数组的声明(定义)、初始化。数组声明之后,可通过下标操作各元素。如 int array[3] = {0,1,2}; array[2] = 6;   同时,应注意非法索引造成的数组下标越界的问题,如array[-2];(等价于*(arrar - 2) 但arrar - 2指向何地址无从可知)和  array[3] = 9;(对于int array[3] = {0,1,2};而言 array[0]~array[2]共3个元素,并没有arry[3]这个元素)
    注意:C++11 大括号{}初始化器适用于所有类型的初始化,列表初始化中=可以省略。

(2)代码实践
    运行结果:


    代码:#include <iostream>
#include <string>

using namespace std;

//数组
void array()
{
int array1[3] = { 0, 1, 2 }; cout << array1[2] << endl;
int array2[] = { 3, 4, 5 }; cout << array2[2] << endl;
//前两种较常用 下面这种数组声明同时初始化的方式仅仅为了展示=省略的情况
int array3[] {6, 7, 8}; cout << array3[2] << endl;

array1[2] = array2[0]; cout << "Now array1[2] = " << array1[2] << endl;

}

int main()
{
//【01】数组
array();

system("pause");
return 0;
}
//两种方式使运行窗口不立即消失:
//1 cin.get();
//2 system("pause");

3 结构

(1)如何使用
  定义:"结构---存储多个不同类型的值"  --- 其实C语言风格的结构体最重要的意义在于 “它是类的基石”

          --- (->) 间接成员运算符 结构体指针可访问结构体成员    (.) 直接运算符 
  声明通用格式 :
                         struct structName                         {                               typeName variate;
                               typeName variate;                                ...
                         };

  概念理解: struct 关键字  structName 自定义结构体类型名,可近似理解为自己定义的一个数据类型,能够像int那样使用。例子见 “(2)代码实践 ”。

  使用:成员运算符“.”访问结构体成员 或者 联用指针“->”访问

  注意:错把结构体定义当成函数定义,如:结构定义 struct stu{...}; 函数定义 void stu(){...}
(2)代码实践

    运行结果:


    代码:
    
#include <iostream>
#include <string>

using namespace std;

//结构定义
struct stu
{
char name[20];
short id;
string sex;
}stu1,stu2 = { "犹唱", 2, "女" },arr[2],*pt;

//结构函数struct_stu()
void struct_stu()
{
stu1 = { "隔江", 1, "男" };
stu stu3 = { "后庭花,", 3, "女" };
cout << stu1.name << stu2.name << stu3.name;

stu* p = &stu3;
strcpy_s(p->name,"商女不知亡国恨。");//使用C语言风格的strcpy()函数会一直报错 提示你用strcpy_s()//strncpy()用法 文末有
cout << p->name << endl;

strcpy_s(p->name, "鱼水相欢");

p->id = 4;
p->sex = "女";
cout << p->name << " " << p->id << " " << p->sex << endl;

    //注意!!!错误用法://cout << *(p->name);//cout << *p->name;
    //以上是结构体变量、结构体指针示例 下面是结构体数组示例
   
    arr[0] = { "胸怀", 8, "男" };
    arr[1] = {};
    cout << arr[0].name << " " << arr[0].id << " " << arr[0].sex << endl;
    cout << arr[1].name << " " << arr[1].id << " " << arr[1].sex << endl;

}
int main()
{
    //【02】结构
    /* 结构声明 声明的同时定义 */
    struct_stu();
    system("pause");
    return 0;
}

//两种方式使运行窗口不立即消失://1 cin.get();//2 system("pause");

//strncpy()用法
/*
strncpy()用来复制字符串的前n个字符,
其原型为:​char * strncpy(char *dest, const char *src, size_t n);
【参数说明】dest 为目标字符串指针,src 为源字符串指针。
            strncpy()会将字符串src前n个字符拷贝到字符串dest。
             不像strcpy(),strncpy()不会向dest追加结束标记’\0’,这就引发了很多不合常理的问题。
               注意:src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。
【返回值】返回字符串dest*/

【补充】 按位操作与硬件设备寄存器

创建与某个硬件设备上的寄存器对应的数据结构 方法:使用按位运算符

4 指针

(1)引子

        之前讨论:【补充2】将信息保存到计算机中的一般型策略
        为将信息存储到计算机中,谨记两条准则:存什么?存哪里? 一般采用的策略是:使用变量(原文->具体内容点击打开链接查看
        计算机可以通过变量(变量名、值、类型)的策略将她的相关信息存储在相应内存单元里(变量名代指内存单元)

        那么现在有没有新的策略来使得信息更好地存储在计算机中呢?答案显而易见。
        我们使用变量更多地是使用变量名来供给计算机进行查询和其他操作,变量名实际上代指相应的内存单元,那么内存单元的         地址就变得很重要,如果有个量能直接存储地址就好了,为此引入 指针变量。

(2)理解
   定义:"指针---存储地址,将数据所处的位置告诉计算机的变量" 

                 --- * 运算符 被称为间接值或解除引用运算符,将其应用到指针可得带该地址处存储的值
   为什么说“指针策略是C++内存管理编程理念的核心”呢?
   从面向对象和面向过程的角度考虑的话,前者更强调在运行阶段决策,后者则更多地在编译阶段决策。运行阶段决策提供了灵活性,可以根据当前情况进行适当调整。如 有的数组需要10个元素的大小就够了,而有少数数组需要100个元素。如果编译阶段决策的话,就要以100个元素的数组为标准,但是这样大大浪费了存储空间!这样就需要一个根据输入适当调整大小的动态数组。

                ---运行阶段---程序正在运行,与当前活动有关    编译阶段---编译器将程序组合起来,不论当前活动如何都按照预定计划执行
   为此,C++采用的策略是:使用关键字new申请正确数量的内存以及用指针来跟踪新分配的内存位置
                ---目前,在我的理解中,首先动态数组能够根据当前输入适当调整数组大小,这是极好的。其次,通过指针变量可以访问和操作指针变量指向的数据。最后,指针变量所占字节大小一定(4bytes 目前我接触的操作系统都是这样的,如windows ubuntu等)。
   注意:本质上来讲,指针作为一种特殊变量,其值存储的是她指向的对象的地址。指针可理解为存储地址的变量。
对于指针,只定义不初始化会造成野指针。不知情的情况下,对野指针进行操作会有严重后果。

(3)代码实践
    运行结果:


    代码:#include <iostream>
#include <string>

using namespace std;

//结构定义示例
struct stu
{
char name[20];
short id;
string sex;
}stu1,stu2 = { "犹唱", 2, "女" },arr[2],*pt;

//指针示例
void point()
{
//数组指针 与 指向数组的指针
//结构体指针
//指向指针的指针

int a = 0, b = 1, array[2] = { 2, 3 };
int* array_p[2] = { &a, &b };//数组指针 元素为地址
int* p = array;//指向数组的指针 //等价于 int* p = &array[0];
stu* stu_pt = &stu2;//指向结构体的指针
int** q = &p;//指向指针的指针 //q存的是p的地址,即q指向p,而p又存的是数组首元素的地址,即p指向数组array[0];

cout <&
ef12
lt; "数组指针 " << *array_p[0] << " " << **(array_p + 1) << endl;
cout << "指向数组的指针 " << *p << " " << *(p + 1) << endl;
cout << "结构体指针 " << stu_pt->name << " " << stu_pt->id << " " << stu_pt->sex << endl;
cout << "指向指针的指针 array[1] = " << *(*q + 1) << endl;

     //*q表示取出q对应存储单元里的值---p的地址
     //p又指向数组array首元素 *p则取出p所指向的地址所对应内存单元里的值
     //(p+1)表示首元素的下一个元素的地址 *(p+1)表示取出该值

}

int main()
{
    //【03】指针
    /* 指针作为一种特殊变量,其值存储的是她指向的对象的地址 */
    point();

    system("pause");
    return 0;
}

5 字符串

(1)C++处理字符串有两种方式:C风格  、 基于string类
    C风格字符串特殊性质:必须以空字符\0结尾,其ASCII码为0   作用:标志字符串的结尾
    常用声明:char str[] = "C str";//表示字符串 空字符被隐式包含在字符串结尾 双引号括起

                                                   //但是,单引号括起的字符,如'A'就不是字符串,她等价于ASCII码表中的一个值

    注意点:字符串常量 字符串 双引号括起 字符串结尾隐式包含空字符 如"A" 等价于 "A\0"
                  字符常量  字符  单引号括起  在ASCII系统上,仅仅是码值的一种表示方法 如 'A' 只是65的另一种写法

    C++有很多处理字符串的函数:cout --- 逐个处理字符串中的字符,直到达到空字符为止。
                                                    cin --- 使用空白(空格、制表符、换行符)确定字符串的结束位置,并自动在字符串结尾添加空字符。
                                                    cin.getline()、cin.get()面对行的输入,读取一行输入,直到达到换行符。随后,前者将换行符舍弃,后者则将其保留在输入序列。函数原型、用法等不赘述。  ---可参考C++ Primer Plus 第六版 4.2.4
     C++ string类简要知识点见下面的代码实践

(2)代码实践
    运行结果:


    代码:
#include <iostream>
#include <string>

using namespace std;

//字符串示例
/* 发送方将想要发送的数据以一定格式打包 这里格式为:str@str_cmd&str_other */
string string_str()
{
string str, str_cmd, str_other, str_flag = "@", str_flag2 = "&";

cout << "请输入要发送的字符串:";
cin >> str;
cout << "输入的字符串为:" << str << endl;

cout << "请输入要发送的命令(正整数1~10):";
cin >> str_cmd;
cout << "输入的命令为:" << str_cmd << endl;

cout << "请输入要发送的其他字符:";
cin >> str_other;
cout << "输入的其他字符为:" << str_other << endl;

//发送方如何让多个字符串打包发送?
str = str + str_flag + str_cmd + str_flag2 + str_other;
cout << "发送方最终发出的数据包为: " << str << endl;

return str;
}

/* 接收方如何解析接收到的数据包-这里采用字符串分割的方式,以及在分割结束后如何对这些数据进行处理 */
void str_deal(string* p)
{
//用到的各函数原型:
//size_t find ( const string& str, size_t pos = 0 ) const;//注意:从0开始 和 第0+1个位置的区别
//在字符串str(str、s或c)中,从第pos+1个位置开始搜寻,返回第一次 与str内容一致的字符串的位置(默认pos = 0)

//size_t find(const char* s, size_t pos = 0) const;

//string substr ( size_t pos = 0, size_t n = npos ) const;//注意: 左闭右开区间的n个
//返回一个子字符串,该子字符串复制了 原字符串中的 第pos+1个开始的n个字符 (左闭右开)(只写pos不写n的话,默认后面的全复制)

cout << "接收方要处理的数据是:" << *p << endl;

string str_m = *p, str_flag = "@", str_flag2 = "&";
size_t find_place = str_m.find(str_flag, 0);//"@"第一次出现的位置 第(find_place+1)个
cout << "find_place = " << find_place << endl;
size_t find_place2 = str_m.find(str_flag2, 0);//"&"第一次出现的位置 第(find_place2+1)个
cout << "find_place2 = " << find_place2 << endl;

//0 ~ @ 左闭右开
string str = str_m.substr(0, find_place);//从第(0+1)个位置开始find_place的个字符 即从0到@(左闭右开,包括第0+1那个位置不包括第find_place那个位置)
cout << "str_m = " << str_m << endl;
 
        //@后一个 ~ & 左闭右开
string str_cmd = str_m.substr(find_place + 1, find_place2 - (find_place + 1));//从@后面一个元素开始的(find_place2 - find_place - 1)个字符

        //&后面的全复制 不包括&
string str_other = str_m.substr(find_place2+1);//从&后面一个元素开始到字符串结束

cout << "str = " << str << endl;
cout << "str_cmd = " << str_cmd << endl;
cout << "str_other = " << str_other << endl;

//在分割结束后如何对这些数据进行处理 简单示例
if (str_cmd == "1")
cout << "Deal Success!" << endl;

}

int main()
{
//【04】字符串
/* 数据包传输的时候,发送方如何让多个字符串打包发送,接收方又如何对数据包进行解析 */
string str = string_str();
cout << "接收方接收到的数据包为:" << str << endl;
string* p = &str;
str_deal(p);

system("pause");
return 0;
}


6 共用体、枚举

(1)共用体、枚举简要介绍:
    共用体、枚举都是结构的特殊例子,可以参照结构理解。
    对于共用体,她的长度为其最大成员的长度。用途是,当数据项使用两种或者更多格式的时候(不会同时使用),可以节省空间。

     枚举,常被用来创建符号常量,可代替const、define等。默认枚举量第一个为0,第二个+1,以此类推,除非有显式声明的值覆盖默认值。
(2)代码实践
    运行结果:


    代码:
#include <iostream>
#include <string>

//#define ONE 1
using namespace std;

//枚举
/* 常被用来创建符号常量 可代替const、#define 但不要犯重定义的错误 */
/* 默认枚举量第一个为0,依次+1,除非有显式声明的值覆盖默认值 */
void f_enum()
{
//符号常量
//const int ONE = 1; cout << ONE << endl;
enum sign{ ZERO, ONE, TWO }; cout << ONE << endl;

//显式值覆盖默认值
enum sign2{ ZERO2, ONE2 = 7, TWO2 }; cout << "ONE2 = " << ONE2 << " " << "TWO2 = " << TWO2 << endl;

}

int main()
{
//枚举
f_enum();

system("pause");
return 0;
}


8 new delete内存管理 动态数组、结构   数组替代品:vector类 array类

(1)对new的一些理解:
    new的作用:程序员告诉new要给什么类型的数据进行内存分配,new就会找到合适的内存块,并返回其地址。常用指针变量去追踪改地址。
    格式:typeName* pointer_name = new typeName;

    使用new的好处:面向对象 在运行阶段决策 更加灵活
    为什么说“指针策略是C++内存管理编程理念的核心”呢?
   从面向对象和面向过程的角度考虑的话,前者更强调在运行阶段决策,后者则更多地在编译阶段决策。运行阶段决策提供了灵活性,可以根据当前情况进行适当调整。如 有的数组需要10个元素的大小就够了,而有少数数组需要100个元素。如果编译阶段决策的话,就要以100个元素的数组为标准,但是这样大大浪费了存储空间!这样就需要一个根据输入适当调整大小的动态数组。

                ---运行阶段---程序正在运行,与当前活动有关    编译阶段---编译器将程序组合起来,不论当前活动如何都按照预定计划执行
   为此,C++采用的策略是:使用关键字new申请正确数量的内存以及用指针来跟踪新分配的内存位置
    为什么new delete必须成对出现?
        只是用new却不delete会造成内存泄漏。因为new申请的变量存储在堆区,而局部变量是在栈区被自动申请的。堆区变量不会因为子函数生命周期的结束而自动释放,反而,对于指向new申请内存地址的指针而言,因为存储在栈区的局部变量---指针的释放,对于真正的堆区变量地址反而没有指针可以再次追踪她,这就造成了内存泄漏。但存储在栈区的局部变量会因为子函数生命周期的结束而自动释放,不用担心所谓内存泄漏。
        故,使用new的金科玉律是 :new delete必须成对出现!
        同时,对于delete而言,不能使用delete删除已经声明的变量的内存,只能用于释放new申请的内存;
            不能使用delete删除已经释放的内存,但对空指针使用delete是安全的。
(2)普通数组  vector类  array类 之间的区别与联系
普通数组有缺点,如不方便、不安全;也有优点,如效率高。那么,如何解决普通数组的这些问题呢?
为此,引入数组代替品。方便安全但是效率较普通数组低的vector模板类,vector类最大的特点是可以在堆区创建动长数组;功能同样较多的array模板类与vector相比,继承了普通数组的高效,但无法创建动长数组,因为array类创建的数组存储在栈区。

 存储位置优点缺点
普通数组效率高不方便、不安全
vector模板类实例化的数组可创建动长数组在运行阶段决策、可创建定长数组,方便、安全效率较普通数组低
array模板类实例化的数组效率高,只可创建定长数组在编译阶段决策,方便、安全不可以创建动长数组
(3)代码实践:
    运行结果:


    代码:#include <iostream>
#include <string>
#include <vector>
#include <array>

//#define ONE 1
//尝试用枚举而不是const、define来定义符号常量
enum sign{ SIZE = 3 };
//const int SIZE = 3;

using namespace std;

//结构定义
struct Stu
{
string name;
short age;
short id;
}stud = { "heihei", 14, 1 };

//枚举
/* 常被用来创建符号常量 可代替const、#define 但不要犯重定义的错误 */
/* 默认枚举量第一个为0,依次+1,除非有显式声明的值覆盖默认值 */
void f_enum()
{
//符号常量
//const int ONE = 1; cout << ONE << endl;
enum sign{ ZERO, ONE, TWO }; cout << ONE << endl;

//显式值覆盖默认值
enum sign2{ ZERO2, ONE2 = 7, TWO2 }; cout << "ONE2 = " << ONE2 << " " << "TWO2 = " << TWO2 << endl;

}

//new delete 动态数组、结构
/* 指针、数组名 共同点:都用来表示地址 区别:指针是变量 数组名是常量 */
void f_newdelete_array_struct()
{
//【01】静态数组
int arr[3] = { 0, 1, 2 };

cout << "静态数组: " << endl;
for (int i = 0; i < 3; i++)
cout << arr[i] << " ";
cout << endl;
cout << "/*****************************/" << endl;

//【02】动态数组
/* 这样就算是动态分配了吗?为什么我感觉不到运行阶段决策的灵活性呢? */
int* pt = new int[3];//delete会把new的内存释放掉,但在该函数的声明周期内指针变量pt依然可以使用,即可以指向其它的变量、结构等//若再定义pt则会造成错误:pt的重定义
int* qt = pt;//防止一个qt被重复delete而导致未知行为

cout << "动态数组: " << endl;
*pt = 10; cout << "pt[0] = *pt = " << *pt << endl;
*(pt + 1) = 11; cout << "pt[1] = *(pt+1) = " << *(pt + 1) << endl;
pt[2] = 12; cout << "pt[2] = *(pt+2) = " << pt[2] << endl;

cout << "p[3] = ";
for (int i = 0; i < 3; i++)
cout << pt[i]<<" ";
//delete [] qt;//防止重复delete已经释放掉的new内存//怎么知道这样做是对还是错误的???
delete [] pt;
cout << endl;
cout << "/*****************************/" << endl;

//运行阶段手动输入数组元素个数,这如何做?体现了面向对象编程的运行阶段决策的灵活性了吗???
/* 数组个数为SIZE个 */
//cout << "请决定您想要输入的元素的个数:";
//cin >> SIZE;//对于流运算符>> 会报错:没有相匹配的重定义
//int arr2[SIZE];//显然这种静态地运行阶段决策语法不允许 不能实现对元素个数的自定义

/* 数组个数仍为SIZE个,但我用new试试 */
//int* pt = new int[SIZE];
//cin >> SIZE;//也会报同样的错
//下面,会在【04】【05】数组的替代品:vector类 array类 中继续尝试

//【03】动态结构
Stu* p = new Stu;//动态结构
Stu* pt2 = &stud; //静态//注意!!!因为同一函数f_newdelete_array_struct()中【02】动态数组用过了int* pt 所以这里改为Stu* pt2 否则会造成pt的重定义

//已经初始化过的静态结构
cout << "静态结构: " << endl;
cout << stud.name << " " << stud.age << " " << stud.id << endl;

//动态结构
p->name = "珍妮";
p->age = 16;
p->id = 2;
cout << "动态结构: " << endl;
cout << p->name << " " << p->age << " " << p->id << endl;

//静态结构通过指针操作
pt2->name = "珍馐";
pt2->age = 10;
pt2->id = 6;
cout << "静态结构: " << endl;
cout << pt2->name << " " << pt2->age << " " << pt2->id << endl;
cout << "/*****************************/" << endl;

/* 数组的替代品:vector类 array类 */

//【04】vector类 - 堆区 - 可将数组设置为动长n
unsigned short n;//动态数组的长度n没必要使用符号常量 因为动长数组的个数n本身就需要是变量啊!
cout << "vector类 - 堆区 - 可将数组设置为动长n " << endl;
cout << "请输入数组元素个数 n = ";
cin >> n;
cout << "--- vector<int>actor(n) " << endl;
vector<int>actor(n);//这是vector类 在堆区创建动态对象的优点
//array<int,n>actor2;//这样写n会报错:必须是常量值

for (int i = 0; i < n; i++)
{
cout << "actor[" << i << "] = ";
cin >> actor[i];
}
cout << "特别地,actor[0] = " << actor[0] << endl;
/*
cout << endl;
cout << "动长数组actor
= ";
for (int i = 0; i < n; i++)
cout << actor
<< " ";//这里进行输出的时候会报错 是因为vector类有她专门的输出函数吗???
cout << endl;
*/
cout << "/*****************************/" << endl;

//【05】array类 - 栈区 - 不可将数组设置为动长n 但效率高 怎么就效率高了???
cout << "array类 - 栈区 - 不可将数组设置为动长n 但效率高 " << endl;
array<int, SIZE>actor2;
cout << "--- array<int," << SIZE << "> " << "actor2" << endl;

//input
for (int i = 0; i < SIZE; i++)
{
cout << "actor2[" << i << "] = ";
cin >> actor2[i];
}
//output
cout << "\n actor2[" << SIZE << "] = ";
for (int i = 0; i < SIZE; i++)
cout << actor2[i] << " ";
cout << endl;
}

//函数指针 指针函数
//数组指针 指针数组

int main()
{
//枚举
//f_enum();

//new delete 动态数组、结构
f_newdelete_array_struct();

system("pause");
return 0;
}


三 总结
                                                                                                                                                                  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: