第一节 数组
数组的概念和数组储存
⭐数组是在内存中顺序连续存储的一组元素。一维数组是简单地按照下标顺序连续存储,多维数组元素也是顺序连续存储的。
⭐数组名可以看作一个指向数组首地址的指针常量。
数组声明和使用
- 一维数组:
int a[n];
- 二维数组:
int a[m][n];
( 在C#中为int[] a = new int[m, n]; )
数组初始化
初始化就是声明数组时赋初值。
int a[3] = {1, 2, 3};
CRE: 在C#中为
int[] a = new int[3]{1, 2, 3};
,注意区别。
数组做函数参数
⭐和普通变量做参数时不同,使用数组做函数参数时,传递的是地址。即在被调函数中改变元素值,主调函数中相应的元素值也会改变。 (同C#,C#数组是引用类型)
⭐因为数组实际上是个指针。(即func(T arr[])
等同于func(T * arr)
)
对象数组
⭐数组的元素不仅可以使基本数据类型,也可以是自定义数据类型。
⭐初始化:Object o[3] = { Object(), Object(), Object() };
C++11的array模板类
它的长度是固定的。它并非STL容器,但是定义了一些成员函数,可将很多标准STL算法用于array对象。例如copy()
和for_each()
。
第二节 指针
内存空间访问方式
⭐计算机内存储器被划分为一个个存储单元。存储单元按一定的规则编号,这个编号就是存储单元地址,地址编码的基本单位是字节。
⭐可以通过变量名访问内存空间,也可以直接通过地址访问内存空间。(在变量获得内存空间是的同时,变量名就成了相应内存空间的名称,在变量的整个生命周期内都可以通过这个名字访问该内存空间)
⭐在函数间传递大量数据时,如果不传递变量值而是只传递变量地址,就可以减少系统开销提高效率。
指针和引用
- 指针:
⭐在C/C++中用来存放内存单元地址的变量类型,称为指针。
⭐通过变量名访问变量是直接的。通过指针访问一个变量是间接的。
- 引用:
⭐引用是在C++中才引入的概念。是一个变量的别名,对引用的任何操作就是对变量的操作。
⭐非常量引用的初始值必须是左值。
- 指针运算符
*
:
⭐出现在声明语句中时:表示声明的是指针。例如int *
表示int型指针。
⭐出现在执行语句中时:用于解析(dereference),表示获取指针指向的变量的值。
- 取地址运算符
&
:
⭐出现在声明语句中时:表示声明的是引用。例如”obj & rf”表示声明了一个obj型的引用rf。
⭐出现在执行语句中时:用于获取对象地址。
指针和引用的关系
⭐指针和引用有很多相似之处。他们都可以用于双向传参减少开销。
⭐指针是一种底层的机制,而引用时较高层的机制。在概念上,引用是另一变量的”别名”,把”地址”这一概念隐藏起来了,但引用的实现还是靠地址。
-
区别:
⭐指针可以是空指针,指针可以任何时候初始化,普通指针可以多次赋值。而引用在创建时必须初始化,其后就不能更改。(所以有指针常量,但没有引用常量,因为引用本就有常量性质。)
⭐引用本身占用的内存空间是隐藏的。即指针对象可以被取地址,引用却不可以,对引用取地址等同于对被引对象取地址。(即:&rf == &obj == ptrObj
) -
总结:
——–建议用引用代替指针———
⭐在函数参数传递时使用引用比使用指针更简洁方便安全。
———–引用有时不能代替指针———–
⭐用new创建的对象或数组,需要用指针而不是引用来存储。
⭐数组形式传递实参时。需要用指针类型接受参数。( 即func(T arr[])等同于func(T * arr) )
指针详解
- 指针初始化和赋值:
⭐初始化语句形式:T * 指针名 = 初始地址
⭐赋值语句形式:指针名 = 地址
- 常量指针和指针常量:
⭐可以声明指向常量的指针(常指针),但是此时不能改变该对象的值。
⭐可以声明指针类型的常量,此时指针本身的值不能被改变。
- 指针运算:
————–指针关系运算——————–
⭐指针可以和整数0之间进行比较。(0或者NULL专用于表示空指针,不指向任何有效地址。NULL是一个被定义为0的宏)
⭐两个同类型指针可以进行比较,相等则表示它们指向同一地址。
⭐不同类型指针之间关系运算无意义。
⭐指针与非零整数之间关系运算无意义。
————–指针加减运算——————–
⭐慎用指针运算,要确保运算结果指向的地址是程序中分配使用的地址。
⭐指针加减运算:ptr ± n 代表ptr所指位置向后/前第n个单位长度的地址,单位长度取决于指针类型。
⭐指针的运算和数组的操作是类似的。( 指针(ptr-1)
指向的数据*(ptr - 1)
等价于ptr[-1]
)
———————-用指针处理数组元素——————–
⭐指针加减运算的特点使得指针特别适合处理数组。(地址arr == &arr[0]
) (数据*arr == arr[0]
)
- 指针数组:
⭐每一个元素都是指针的数组称为指针数组。(声明:int * p[10];
)
⭐指针数组可以模拟二维数组,但二者有根本区别。
-
指针作参数: ⭐建议用引用代替指针作参数,更方便简洁安全。
-
指针型函数(返回值为指针的函数):
⭐定义:T * Func(param){ ... }
⭐使用指针型函数的最主要目的就是在函数结束时把大量数据从被调函数返回主调函数。
函数指针
⭐程序运行时,不仅数据要占据内存空间,执行程序的代码也被调入内存并占据一定的空间。函数的函数名就表示函数的代码在内存中的起始地址。在程序中可以像使用变量名一样使用函数指针来调用函数。
⭐如果要声明多个函数指针,建议使用typedef关键字。
-
声明:
T (* ptr)(params)
-
赋值:
ptr = Func;
– 其中Func(params){...}为
返回值为T的函数
ptr = [](params) -> T{ ... }
Lambda表达式匿名函数
- 调用指向的函数:
ptr();
对象指针
- 一般概念:
⭐和基本类型变量一样,每个对象在初始化后都在内存中占用一定的空间。因此可以用对象名,也可以通过对象地址来访问一个对象。对象指针就是用于存放对象地址的变量。
⭐对象占据的内存空间只用于存放数据成员,函数成员不在每一个对象中存储副本。
—————访问对象成员的两种等价语法—————
对象指针名->成员名
(*对象指针名).成员名
this
指针:
⭐this
指针是一个隐含于每一个类的非静态成员函数的特殊指针。this指针指出了成员函数当前操作数据所属的对象。
- 指向成员的指针:
————–非静态成员———–
声明:T ClassName :: * ptr = &ClassName::MemberName;
访问:obj.*ptr;
或者 objPtr -> *ptr;
————–静态成员—————
声明:T * ptr = &ClassName::MemberName;
访问:*ptr;
第三节 动态内存分配(new)
概述
⭐动态内存分配技术可以保证程序运行过程中按照实际需要申请适量的内存,使用结束后还可以释放。这种在程序运行过程中申请和释放的存储单元也称为堆对象。
⭐建立和删除堆对象的两个运算符:new
运算符的功能是动态分配内存,或者称为动态创建堆对象。delete
运算符用来删除由new
创建的对象并释放指针所指内存空间,如果删除的是对象则会调用析构函数。
- 示例:
//动态创建基本类型: int * p = new int(); //如果不带括号则表示分配内存但不赋初值。有括号则用括号内数值来初始化,括号中没有数值则用0来初始化。 //动态创建对象: Object * objP = new Object; Object * objP = new Object(); //动态创建数组: int * p = new int[10]; int * p = new int[10](); //删除: delete[] p; //删除要加"[]",删除整个数组。
注意事项
⭐no new no delete, no malloc no free。
⭐动态分配new
的对象不使用delete
就不会释放,就算在被调函数里动态分配new
也不会自动释放。一直存在到程序结束。
第四节 用vector创建数组
概述
⭐new动态创建的数组无法检测下标越界的错误。C++标准库提供过了被封装的动态数组- vector(向量),它具有各种类型。
⭐与普通数组不同的是,用vector定义的数组对象的元素都会被初始化。
使用
//include:
#include <vector>
//定义:
vector<T> arr(length);
vector<T> arr(length, initValue)
第五节 深复制和浅复制
默认复制构造函数
⭐大多数时候不需要特别编写复制构造函数。因为,隐含的复制构造函数足以实现对象间数据元素的一一对应复制。但是它不总是实用的的,因为完成的只是浅复制。
⭐浅复制在复制指针类变量时,前后两个指针指向同一内存地址,表面上完成了复制,但是并没有形成真正的副本。
深复制
⭐对于指针型成员,在复制构造函数中分配内存空间并复制对应的数据,然后让该指针成员指向该地址。即可实现深复制。
第六节 字符串(string)
概述
C/C++的基本数据类型中没有字符串变量。可以像C语言一样使用字符数组,也可以使用C++标准库中的string类。
- 字符串变量:
char str1[4] = {'1','2','3','\0'}; char str2[4] = "123"; char str3[] = "123"; //三种写法等价
- 字符串常量:
为什么
const char*
类型的相同字符串常量会具有相同的地址?
它被称为常量合并,通常在更高的优先级别启用。编译器只需获取所有唯一的常量值并将其压缩。
const char * str2 = "123";
已知常量指针不能赋值给非常量指针,所以必须加const关键字。
- 传统C字符串和C++字符串类:
传统C字符串 – 以空字符\0
结束。称为NBTS(null-teminated string)。
C++的string类 –包含大量的内容,其中包含若干构造函数、重载运算符、以及各种工具。
可以调用c_str()
将C++字符串转换为C字符串。
string类输入
C:
char info[100];
cin >> info;//1、read a word.
cin.getline(info, 100); //2、read a line, discard \n
cin.get(info, 100); //3、read a line, leave \n in queue
cin.get(info, 100, ':'); //4、read up to :, discard :
C++:
string info;
cin >> info;//1、read a word.
getline(cin, info);// 2、read a line,discard \n
getline(info, ':');// 3、read up to :, discard :
string类
⭐C++标准库将面向对象的串的概念引入,预定义了string类。使用string类必须包含string头文件,string类封装了串的属性并提供了一系列允许访问这些属性的函数。
- 构造函数:
string(); //默认构造函数 string(const string & rhs); //复制构造函数 string(const char * s); //用指针所指的字符串常量初始化。 string(const string * s, unsigned int n); //在s指向的字符串中截取前n个字符初始化。 string(const string & rhs, unsigned int pos, unsigned int n); //在字符串rhs中从pos开始截取n个字符初始化。 string(unsigned int n, char c); //将字符参数c重复n次初始化。 //...
- 字符串操作符:
[]
访问某个下标的字符
+
连接字符串
=
赋值(更新)
+=
附加
==, !=, >, <, <=, >=
相等、不等、大于、小于。。。
- 常用成员函数:
string append(const char * s); string assign(const char * s); int compare(const string & str) const; string & insert(unsigned in p0, const char *); string substr(unsigned int pos, unsigned int n); unsigned int find(const basic_string &str) const; unsigned int length() const; //也可以用size()以兼容STL。 void swap(string & str);
- 使用c_str()函数得到字符数组的注意事项:
函数c_str()返回一个指向正规C字符串的指针常量, 内容与本string串相同.(其实它指向的是string对象内部真正的char缓冲区),所以返回const,以防止用户的修改。
所以不要用花括号内临时string对象来得到字符数组指针。因为花括号结束时临时对象被析构,指针就成了垃圾内容。解决方法:可以赋值一次。也可以使用动态分配new的string对象。也可以用strcpy()函数来复制字符数组。
补充: 函数
//基本
getline() //从流中一行一行读取字符串。参数1:要从其中提取字符串的输入流。参数2:从输入流读取的字符将放入的字符串。参数3:行分隔符。
stod() //to double
stof() //to float
stoi() //to int
stol() //to long
stold() //to long double
stoll() //to long long
stoul() //to unsigned long
stoull() //to unsigned long long
to_string() //将一个值转换为 string。
to_wstring() //将一个值转换为宽字符串。
//查找相关
find()
rfind()
find_first_of()
find_last_of()
find_first_not_of()
find_last_not_of()
//其他
capacity()//容量(返回当前分配给字符串的内存块大小)
reserve()//预留(预留足够空间,避免不必要的重复分配)
(END)