文章目录
一、为什么要引入数组?
- 数组是由一组具有 相同数据类型 的元素按照一定的规则组成的集合,用 数组名 表示存储区域的地址,用 下标变量 来标识数组中的每个元素
- 用数组类型可以描述许多有意义的对象,便于处理同一性质的成批数据,如向量、矩阵等
- 数组和指针有着密切的联系,任何能由数组下标完成的操作也都可以用指针来实现
- 数组分类: 按照数组
元素类型
可分为:数值数组、字符数组、指针数组、结构数组等;按照维数
可分为:一维数组、二维数组、多维数组等
二、一维数组(对应一个线性表)
- 一维数组的定义、存储和初始化
- 定义:数据类型 数组名[整型常量表达式];
- 存储:数组名/&数组名[0] 存放该数组的首地址,是一个地址常量(占用内存大小
sizeof(数组名)
) - 初始化:在定义数组的同时,为数组元素赋初值,
数据类型 数组名[整型常量表达式] = {常数1, 常数2, ... , 常数n};
- 此时整型常量表达式可省略,数组长度由初值个数确定
- 当整型常量表达式的值大于初始化值的个数时,其余的元素系统自动赋 0
- 全局数组若不初始化,编译器将其初始化为
零
;局部数组若不初始化,内容为随机值
- eg:
int myNumbers[5] = {0}; // initializes all integers to 0
- 数组元素的引用
- 下标方式:
数组名[下标]
- 地址方式 :
*(地址)
- 指针方式:
*指针变量名
- 下标方式:
- 数组排序:选择法、冒泡法、插入法、快速排序法
- 求取数组元素的个数:
- C++ 中的
std::size
方法:std::size(ptr)
- C 和 C++ 中通用的 sizeof 方法:
sizeof(ptr)/sizeof(type)
- C++ 中的
(c)end - (c)begin
方法:std::end(ptr) - std::begin(ptr)
- C++ 中的
- 数组元素的基本操作
// 假设有定义:int j, a[N]; // 数组元素的输入 for (j=0; j<N; j++) { cin >> a[j]; } // 数组元素的输出 for (j=0; j<N; j++) { cout << a[j] << " "; } // 数组元素的求和 sum=0; for (j=0; j<N; j++) { sum += a[j]; } // 求数组中的最小元素 min=a[0]; //假设第一个元素值最小 for (j=1; j<N; j++) { if (a[j] < min) { min = a[j]; } } // 求数组中的最小元素下标 imin=0; //假设 0 为最小元素下标 for (j=1; j<N; j++) { if (a[j] < a[imin]) { imin = j; } }
三、二维数组(相当于一个矩阵)
- 二维数组的定义和初始化
- 定义:
数据类型 数组名[常量表达式1][常量表达式2];
- 初始化:
- 定义:
- 二维数组的基本操作同一维数组相似, Note:任何维数的数组在内存中都是以线性表的形式连续存放的:
四、字符数组
1、C 风格字符串
- 字符串的特点:
- C 中没有专门的 字符串变量,但提供了 字符数组和字符指针 来处理字符串的方法
- C 中的字符串是以
'\0'
结尾的字符数组,若不以'\0'
结尾,则不是一个字符串,只是普通字符数组 - 以 内存的首地址 来代表该字符串
- 字符串的三种形式:
- 字符数组(
栈区
):若需修改字符串内容,且不用考虑内存分配问题,优选以字符数组的形式来处理字符串(适用于占用字节数比较少的字符串)- 用 char 型数组 来表示一串字符,用格式字符 %s 使其以字符串的形式打印出来,
eg:printf("The str is: %s\n ")
- 字符数组的初始化:
char str[6] = { 'h', 'e', 'l', 'l', 'o','\0' } = { 'h', 'e', 'l', 'l', 'o', 0};
,如果一串字符没有以'\0'
(字符串结束符) 结束,则 printf 或 cout 会一直尝试打印下去;直到遇到'\0'
结束打印,'\0'
之后的字符不会被打印出来char str[6] = "hello";
编译器自动添加字符串结束符'\0'
,字符串的长度为 5(strlen(str)
),所以定义的字符数组容量必须大于等于 6(sizeof(str)
)- Note:字符串处理以
'\0'(数字0)
作为结束符,若后面还有字符,则不会输出
- 用 char 型数组 来表示一串字符,用格式字符 %s 使其以字符串的形式打印出来,
- 动态字符串(
堆区
):- 可以动态地分配一块内存,然后在这块内存里存放一串字符
char* str = (char*) malloc(12); str[0] = 'h'; str[1] = 'i'; ......
- 字符串常量(
常量区
):- 字符串常量是用
双引号
括起来的字符序列, 系统在字符串常量最后自动加字符串结束符号'\0'
(ASCII 码编码值为 0 的字符,它是一个空字符str[i] = 0; 等价于 str[i] = '\0';
”) - 字符串常量的类型为:
char* a = "hi"; // 省略了字符数组的定义
注意:C 中没有char a = "hi";
这种写法,因为 C 中没有专门的字符串变量 - 字符串常量
"hi"
(实质是字符串的首地址) 是char*
类型的, a 这个指针指向的内存是只读的,存在常量数据区
- 字符串常量是用
- 字符数组(
#include<stdio.h>
#include<stdlib.h>
int main() {
// 1、栈区中存放的字符串可以被修改
char stack_arr[] = "hello world";
stack_arr[0] = 'm';
stack_arr[1] = 'a';
stack_arr[2] = 'n';
// 2、堆区中存放的字符串可以被修改
char *heap_arr = (char *) calloc(1, 12);
*heap_arr = 'm';
*(heap_arr + 1) = 'a';
heap_arr[2] = 'n';
// 3、常量区存放的字符串不能被修改,可以读取
char *const_arr = "hello world";
// *const_arr = 'm'; // error
printf("stack arr is: %s\n", stack_arr); // stack arr is: manlo world
printf("heap arr is: %s\n", heap_arr); // heap arr is: man
printf("const arr is: %s\n", const_arr); // const arr is: hello world
if (heap_arr) {
free(heap_arr);
heap_arr = NULL;
}
return 0;
}
- 字符串的操作:
#include <string.h>
- 字符串的长度:
strlen("str") = 3
,字符串的长度不包括末尾的'\0'
;sizeof("str") = 4
,使用sizeof
运算符得到的长度包含末尾的'\0'
- 字符串的复制:
strcpy(dst, src);
,只需传入字符串的首地址即可 - 字符串的比较:
int flag = strcmp(a, b); a > b, flag = 1; a = b, flag = 0; a < b, flag = -1
- 其它字符串处理函数:
gets()/fgets()/puts()/fpus()/strncpy()/strcat()/strncat()/strncmp()/strchr()/strstr()/strtok()/atoi()/atof()
- 字符串的遍历、插入、删除、分割
- 字符串的长度:
#include <stdio.h>
#include <string.h>
// 1、字符串的遍历、复制及长度统计
int get_str_length(const char *str, char *dst) {
int count = 0;
while (str[count]) // 以字符串的结束符 0 作为判断条件
{
printf("%c ", str[count]); // 字符串遍历
dst[count] = str[count]; // 字符串复制
count++; // 字符串长度统计
}
dst[count] = 0;
printf("\nThe copied str is: %s \n", dst);
printf("\nThe length of this str is: %d \n", count);
return 0;
}
// 2、删除某个字符,只需将其后面的字符依次前移即可
char del(char *str, int index) {
int len = strlen(str);
char ch = str[index];
for (int i = index; i < len; i++) {
str[i] = str[i + 1]; // 后面的字符前移
}
return ch; // 返回删除的字符
}
// 3、插入某个字符,从 index 位置开始的字符(包括)依次往后移动,最高位先开始移动,然后空出位置给次高位移动,以此类推
void insert(char *str, int index, char ins) {
int len = strlen(str);
for (int i = len; i >= index; i--) {
str[i + 1] = str[i];
}
str[index] = ins;
}
int main() {
char src[7] = "hello";
insert(src, 3, 'w');
printf("插入的字符'w'后的字符串为:%s \n", src);
return 0;
}
// 4、字符(串)标准输入输出函数
char getchar(void); // char ch = getchar();
int putchar(int c); // putchar(ch);
char* gets_s(char* s); // 连续读取一连串的字符,包括标点符号和空格,遇到'\n'时结束读取
// scanf_s 用于获取单词而不是字符串,所以它在遇到空格时会结束读取
int puts(const char* s); // 只能配合 gets_s 使用,需指定一个 char* 类型的常指针, eg: puts(buf);
// printf 标准格式化输出,功能强大,但不如 puts 效率高
-
字符串格式化:sprintf()/sscanf()
#include<stdio.h>
#include<string.h>
int main() {
char buf[1024] = {0};
char str1[] = "hello";
char str2[] = "world";
int len = sprintf(buf, "%s %s", str1, str2);
printf("buf is: %s, str len is: %d\n", buf, len); // buf is: hello world, str len is: 11
memset(buf, 0, 1024);
sscanf("123456aaaa", "%*d%s", buf); // 跳过前面的数字,输出 buf:aaaa
printf("buf:%s\n", buf);
memset(buf, 0, 1024);
sscanf("123456aaaa", "%7s", buf); // 读取指定宽度的数据,输出 buf:123456a
printf("buf:%s\n", buf);
return 0;
}
- 拼接字符串,拷贝字符串,格式化字符串
sprintf
- strncpy,memcpy,memmove 的区别及工业中用哪个?
2、C++ 中的字符串
- 不同于字符数组(C 风格字符串实现),C++ 中提供了
std::string
,可动态调整其长度,使用更加灵活可靠 - eg:
string greetString ("Hello std::string!");
,通过.length()
方法确定其长度 - Note: 计算字符串的长度时,不包含末尾的字符串结束符(
\0
)
五、静态数组与动态数组
1、静态数组
- 长度在编译阶段就已确定
- 不能存储更多的数据,而且即使有部分元素未被使用,它们占据的内存也不会减少
- C++提供了
std::(c)begin, std::(c)end
(其中 c 为可选项,表示const
)获得指向数组开头与结尾的指针
2、动态数组
- 长度在执行阶段确定,可动态地调整其长度
- 无需在编译阶段考虑其最大长度,可更好地管理内存,以免分配过多的内存,而又不使用它们
- C++提供了
std::vector
来使用的动态数组,eg:vector<int> dynArray (3); // dynamic array of int
3、动态内存分配
-
C++
中的new
和delete
分别用来分配和释放自由存储区中的内存,自由存储区是一种内存抽象,表现为一个内存池
,应用程序可分配(预留)和释放其中的内存,它们与C
语言中malloc()、free()
最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于类来说是不可或缺的,所以在C++
中推荐使用new
和delete
-
在
C++
中,使用new
运算符创建一个结构体变量时,默认情况下,结构体的成员不会被初始化;new运算符只会分配内存空间,并返回指向该空间的指针- 方法1:在结构体定义的时候定义构造函数;使用的时候直接传参进行初始化;
- 方法2:在结构体定义的时候使用默认初始化;
- 方法3:对结构体中的成员变量进行赋值(或先
memset 0
;后面用的时候再赋值)
-
为单个元素分配内存:
Type* Pointer = new Type{}; // request memory for one element and init delete Pointer; // release memory allocated above // 类动态内存分配并初始化 SomeClass* PointerToClass = new SomeClass{"test", 123}; delete PointerToClass ; // eg: new 请求分配内存并不能保证总能得到满足,因为这取决于系统的状态 以及内存资源的可用性 int* pointToAnInt = new int{123}; // get a pointer to an integer and init with 123 delete pointToAnInt;
-
为多个元素分配内存:
Type* Pointer = new Type[numElements]; // request memory for numElements delete[] Pointer; // release block allocated above // eg: new 请求分配内存并不能保证总能得到满足,因为这取决于系统的状态 以及内存资源的可用性 int* pointToNums = new int[10]; // get a pointer to an integer delete[] pointToNums; // 根据用户的要求动态地分配内存块,以便能够存储指定数量的 int 变量(由用户输入) int numEntries = 0; cin >> numEntries; int* myNumbers = new int[numEntries]; cout << "Allocated for " << numEntries << " integers" << endl; for(int counter = 0; counter < numEntries; ++counter) { cout << "Enter number "<< counter << ": "; cin >> *(pointsToInts + counter); } cout << "Displaying all numbers entered: " << endl; for(int counter = 0; counter < numEntries; ++counter) { cout << *(pointsToInts++) << " "; // pointsToInts 所指向的地址改变了 } cout << *(pointsToInts++) << " "; // return pointer to initial position // 调用 delete[]来释放内存时,必须是分配内存时 new 返回的指针地址 pointsToInts -= numEntries; delete[] myNumbers;
-
内存泄露:
- 使用 new 动态分配的内存不再需要后,没有使用配套的 delete 释放
- 程序占用的内存没有及时释放,导致系统可用内存量的减少
- 如果有指针的多个拷贝,只需对其中一个调用 delete
- C++ 没有 自动垃圾回收机制 对程序已分配但不能使用的内存进行清理,所以使用指针来管理内存资源时要特别当心
int* pointToNums = new int[5]; // initial allocation // use pointToNums ... // forget to release using delete[] pointToNums; ... // make another allocation and overwrite pointToNums = new int[10]; // leaks the previously allocated memory
五、指针与引用
1、指针
-
为什么要引入指针?
- 指针可以有效的表示复杂的数据结构,方便的使用数组和字符串,不再需要进行参数拷贝
- 在调用函数时可以获得一个以上的结果(通过结构体或类)
- 能动态分配内存并直接处理内存单元,效率较高(函数参数传递,只复制了地址,相比大型结构体
复制成本低
,但读写成本稍高) - 指针
- Note: 指针也是一种数据类型,指针变量是一种变量
-
指针变量定义:
- 是一种指向内存单元的
特殊变量
,它的内容永远是内存地址 - 占用几个字节的内存跟系统的寻址能力有关,一般 32 位系统占用 4 个字节内存,64 位系统占用 8 个字节内存,不会随着指向数据的数据类型而变
- 声明:
数据类型* 标识符;
*
后面的标识符是指针变量名(用于存放另一变量的地址),*
本身不是变量名的组成部分,只是用来声明后面的标识符是一个指针类型的变量- 指针定义中的数据类型不是指针本身的数据类型,而是指针所指的对象的数据类型,指针变量中地址数据占用的内存不会随着指向数据的数据类型而变
#include <iostream> using namespace std; int main() { cout << "sizeof fundamental types:" << endl; cout << "sizeof(char) = " << sizeof(char) << endl; cout << "sizeof(int) = " << sizeof(int) << endl; cout << "sizeof(double) = " << sizeof(double) << endl; cout << "sizeof pointers to fundamental types:" << endl; cout << "sizeof(char*) = " << sizeof(char*) << endl; cout << "sizeof(int*) = " << sizeof(int*) << endl; cout << "sizeof(double*) = " << sizeof(double*) << endl; return 0; } sizeof fundamental types: sizeof(char) = 1 sizeof(int) = 4 sizeof(double) = 8 // 将 sizeof( ) 用于指针时,结果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关 // 存储指针所需的内存量相同 sizeof pointers to fundamental types: sizeof(char*) = 4 sizeof(int*) = 4 sizeof(double*) = 4
- 是一种指向内存单元的
-
指针初始化:在定义的同时赋初值
- 地址运算符(引用运算符)
&
:&变量,取得变量在内存中的地址
,一般变量第一个字节的地址就是变量的地址 - 读/写内容运算符
*
:*p 用于访问 p 指向的内存(读、写)
,注意,p 为地址
- 地址运算符(引用运算符)
-
指针运算:
-
赋值运算:
int *p, a; p = NULL; // 指针的值为 NULL(0), 表示不指向任何对象 p = &a; // p 指向变量 a
-
指针步长与指针类型:
- 指针步长:指针变量+1,要向后跳多少字节
- 指针类型:不仅决定指针的步长,还决定
解引用*
的时候从给定地址开始取类型大小的字节数
char buf[1024] = {0}; int a = 100; memcpy(buf + 1, &a, sizeof(int)); // 不同数据类型的变量赋值后,解引用需要进行数据类型的强转 printf("*(int *)(buf+1) = %d\n", *(int *) (buf + 1)); // 输出 *(int *)(buf+1) = 100
-
p++
、p--
与p+n
、p-n
运算(p 为指针,n 为整数):- 自增或自减后 p 指向后一个或前一个元素,指针的值变为后一个变量或前一个变量的地址
p 的值+n*sizeof(p 指向的类型)
,p 本身的值并没有变int a = 5; // 假设 a 的地址为 0x0045FE00, 则 a 所占用的内存块为:0x0045FE00~0x0045FE04 这四个字节(一个地址对应一个字节的数据) int* p = &a; // p 指向 a,则 p 的值为 0x0045FE00, p + 2 的值为:0x0045FE00 + 2*sizeof(int) = 0x0045FE00 + 2*4 = 0x0045FE08 // 对于int* 来说,元素大小是 4 字节,所以后移两个元素、自然地址要加 2*4
-
指针相减:两个类型相同的指针可以相减,结果为这两个地址差之间能够存放数据的个数(数据类型为指针所指的类型)
int *p1, *p2; // 假设 p1 指向 1000,p2 指向 1008,则 p2-p1 的值为:(1008 - 1000) / sizeof(int) = 2
-
-
将关键字 const 用于指针:
-
指针指向的
数据为常量,不能修改
,但可以修改指针包含的地址,即指针可以指向其他地方int hoursInDay = 24; const int* pointsToInt = &hoursInDay; // * 在 const 后面,数据(*pointsToInt )是常量,只读 int monthsInYear = 12; pointsToInt = &monthsInYear; // OK! *pointsToInt = 13; // Not OK! Cannot change data being pointed to int* newPointer = pointsToInt; // Not OK! Cannot assign const to non-const
-
指针包含的
地址是常量,不能修改
,但可修改指针指向的数据int daysInMonth = 30; int* const pDaysInMonth = &daysInMonth; // * 在 const 前面,地址(pDaysInMonth)是常量 *pDaysInMonth = 31; // OK! Data pointed to can be changed int daysInLunarMonth = 28; pDaysInMonth = &daysInLunarMonth; // Not OK! Cannot change address!
-
指针包含的地址以及它指向的值都是常量,
不能修改
int hoursInDay = 24; const int* const pHoursInDay = &hoursInDay; *pHoursInDay = 25; // Not OK! Cannot change data being pointed to int daysInMonth = 30; pHoursInDay = &daysInMonth; // Not OK! Cannot change address
-
-
多级指针:
- 二级指针:指向一个一级指针变量地址的指针
- 三级指针:指向一个二级指针变量地址的指针
#include <stdio.h>
int main() {
int a = 10;
// 一级指针变量:指向普通变量
int* p = &a; // *p 就是 a,*p = 10
// 二级指针变量:指向一级指针变量
int** q = &p; // *q 就是 p,**q 就是 a
// 三级指针变量:指向二级指针变量
int*** t = &q; // *t 就是 q,**t 就是 p,***t 就是 a
printf("p is %p, *p is %d!\n", p, *p);
printf("q is %p. *q is %p, **q is %d!\n", q, *q, **q);
printf("t is %p. *t is %p, **t is %p, ***t is %d!\n", t, *t, **t, ***t);
return 0;
}
>>>
p is 0x7ffc591774fc, *p is 10!
q is 0x7ffc59177500. *q is 0x7ffc591774fc, **q is 10!
t is 0x7ffc59177508. *t is 0x7ffc59177500, **t is 0x7ffc591774fc, ***t is 10!
- 安全的使用指针:
- 杜绝野指针:
- 一个指针未赋值时,其值为随机值,此时指向了一个随机的内存地址,称为野指针
- 野指针不会直接引发错误,操作野指针指向的内存区域才会出问题(假如指向了系统启动区,然后又操作了此内存区域,就可能出现问题)
- 函数内无法判断野指针,所以指针变量在创建的同时应当被初始化,要么将指针设置为
NULL
,要么让它指向合法的内存 - Note:
- 指针在 free 或 delete 后(只是把指针所指的内存给释放掉,并没有把指针本身干掉)应立即置空(
free(p); p=NULL;
) - 不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放
- 指针在 free 或 delete 后(只是把指针所指的内存给释放掉,并没有把指针本身干掉)应立即置空(
- 指针只允许指向两个地方:
指向变量、数组或者指向 NULL(0)
使用指针前判断其指向的变量是否已经失效
- 如果指向的目标的生命期已经终结(失效),则该指针也失效
- 提前判断空指针:
int* p = NULL;
指针变量的值为 0 的指针,称为空指针,此时不能使用星号(*p
)进行解引用取值操作,但可以使用p
对其进行赋值操作(指向新的地址)int** p = NULL;
指针变量的值为 0 的指针,称为空指针,此时不能使用星号(**p 或 *p
)进行解引用取值操作,但可以使用(*p 或 p
)进行赋值- 传递空指针:函数内部是有办法处理,判断一下就行
if (p)
- 不同类型的指针不能互相赋值
- 杜绝野指针:
2、引用
a、引用基本语法
- 引用的定义:
Type& Ref = Variable
,&
写到变量左侧叫引用,写到右侧叫取地址- 引用就是为已定义变量起个
别名
- 变量名实质上是一段连续内存空间的
标号
;程序中通过变量来申请并命名内存空间,通过变量的名字即可使用内存空间
- 引用就是为已定义变量起个
- 引用必须初始化,且初始化后不能修改指向(值可以改变);必须确保引用的是一块合法的内存(不能是
NULL
)
int a = 10;
int A = 20;
int& b = a; // 引用必须初始化,给变量 a 取一个别名 b,与 a 共用一段内存
// int& b = A; // 报错,初始化后不可修改指向。但可以操作 b 或 a 可以改变其值
int c = a; // 把 a 的值赋给 c,c 会另外开辟一块内存
b = 100; // 操作 b 就相当于操作 a 本身(指向同一块内存),此时 a,b 均为 100
// 数组中的引用
int arr[10] = {0};
int(&f)[10] = arr; // 注意,需要加 [10]
for (int i = 0; i < 10; i++){
f[i] = i + 10;
}
- 函数中的引用
// 值传递
void ValueSwap(int m, int n) {
int tmp = m;
m = n;
n = tmp;
}
// 地址传递
void PointerSwap(int *m, int *n) {
int tmp = *m;
*m = *n;
*n = tmp;
}
// 引用传递
void ReferenceSwap(int &m, int &n) {
int tmp = m;
m = n;
n = tmp;
}
void test() {
int a = 10;
int b = 20;
ValueSwap(a, b);
// 值传递,不会改变函数外参数的值,此时 a=10, b=20
cout << "a:" << a << " b:" << b << endl; //
PointerSwap(&a, &b);
// 地址传递,地址不会变,但会改变函数外参数的值,此时 a=20, b=10
cout << "a:" << a << " b:" << b << endl;
ReferenceSwap(a, b);
// 引用传递,地址不会变(type & var= type* const var)
// 会改变函数外参数的值,此时 a=10, b=20
cout << "a:" << a << " b:" << b << endl;
}
- 局部变量的引用和静态变量的引用
// 返回局部变量引用
int &TestFun01() {
int a = 10;
return a; // 不要返回局部变量的引用
}
// 返回静态变量引用
int &TestFunc02() {
static int a = 20;
cout << "static int a : " << a << endl;
return a; // 可以返回静态变量的引用
} // 如果函数的返回值是静态变量的引用,那么这个函数调用可以作为左值
b、引用的本质
- 引用的本质:
Type& ref = val; =========> Type* const ref = &val;
,本质是一个常量指针
- C++ 编译器在编译过程中使用常量指针对其进行替换,此过程对用户不可见
- 所以引用不可以改变指向,但可以改变内存中的值,占用内存大小与指针相同
// 发现是引用,转换为 int* const ref
void testFunc(int &ref) {
ref = 100; // ref 是引用,转换为 *ref = 100
}
int main() {
int a = 10;
int &aRef = a; // 自动转换为 int* const aRef = &a
aRef = 20; // 内部发现 aRef 是引用,自动帮我们转换为: *aRef = 20;
cout << "a:" << a << endl; // 20
cout << "aRef:" << aRef << endl; // 20
testFunc(a);
cout << "a:" << a << endl; // 100
return 0;
}
c、指针引用和常量引用
- 指针引用:用一级指针引用可以代替二级指针
- 常量引用:可以修饰形参为只读,主要用在函数的形参,尤其是类的拷贝/复制构造函数
- 将函数的形参定义为常量引用的好处:
- 引用
不产生新的变量
,减少形参与实参传递时的开销 - 如果希望实参随着形参的改变而改变,那么使用
一般引用
,如果不希望实参随着形参改变,那么使用常引用
- 引用
// 常量引用:防止函数中意外修改数据
void ShowVal(const int& param){
cout << "param:" << param << endl;
}
3、指针与引用的区别
- 指针:
- 可以改变其指向
- 可以为空指针
- 地址信息可能非法
- 与引用不同,指针上的算术和关系操作所指的是机器地址,它们是指针本身的值,而不是指向变量的值
- 引用:
- 构造时绑定对象,在其生命周期内不能绑定其它对象,即不能改变其指向(
和指针的区别
);这也导致其不能像指针那样以递增的方式遍历数组 - 不存在空引用,但可能存在非法引用,总的来说比指针安全
- 属于编译期概念,在底层还是通过指针实现;
Type& ref = val; =========> Type* const ref = &val;
,本质是一个常量指针
- 当引用作为函数参数时,如果在函数体内部不会修改引用所绑定的数据,那么请尽量为该引用添加
const
限制;给引用添加const
限定后,不但可以将引用绑定到临时数据,还可以将引用绑定到类型相近的数据,即可进行数据类型的自动转换
- 构造时绑定对象,在其生命周期内不能绑定其它对象,即不能改变其指向(
六、指针和数组
- 获得指向数组开头与结尾的指针 :
std::(c)begin(ptr)
,std::(c)end(ptr)
,其中 c 为可选参数,为 const 的缩写 - 指针和一维数组
- 数组指针:一个指针指向了数组首地址,
int *p = arr;
,p
的类型是int *
,arr 中每个元素都是int
型 - 指针数组:一个数组中的所有元素保存的都是指针,
int *p = arr[5];
,p
的类型是int *[5]
,arr 中每个元素都是int*
型
- 数组指针:一个指针指向了数组首地址,
int a[3] = {10, 20, 30};
int *p;
// 当执行 p = a 或者 p = &a[0] 时,有如下等价关系成立:
p = a = &a[0] // 数组首地址
p + i = a + i = &a[i] // 数组第 i 个元素的地址
*(p + i) = *(a + i) = a[i] = p[i] // 可以把 p 当成数组来用
// p 与 a 的区别:
// 两者同样是表示数组 a 的地址,在表现形式上可以互换,但它们本质不同,p 是地址变量,而 a 是地址常量,不可改变
int myNumbers[100]; // myNumbers 是一个 int 数组,它指向这样的内存单元的开头,即其中存储了 100 个整数
// 每个元素都是指针类型
int* myArrays[100]; // myArrays 是一个包含 100 个元素的指针数组,其中的每个指针都可指向 int 或 int 数组
- 指针和字符串
char s[50] = "hello world"; // 字符串,编译器自动添加字符串结束符 '\0',数据类型为 char [50]
char *p = s; // 亦可直接执行 char* p = "hello world", p 就直接指向了字符串的首字符
// 当执行 p = s 或者 p = &s[0] 时,有如下等价关系成立:
p = s = &s[0];
p + i = s + i = &s[i];
*(p + i) = *(s + i) = s[i] = p[i]; // 可以把 p 当成数组来用,*p = 'h'(首个字符的值)
*p = 'm'; // 将字符数组的首个字符替换为 'm',此时指针变量指向字符数组的第一个元素 'm'
p++; // 此时指针变量指向字符数组的第二个元素
*p = 'i'; // 将字符数组的第二个字符替换为 'i',此时指针变量指向字符数组的第二个元素 'i'
printf("%s\n", s); // 输出 millo world,只要给定数组首地址,然后以字符串形式打印即可输出完整字符数据
printf("%s\n", p); // 输出 illo world,指针变量指向字符数组的第二个元素 'i'
p = "like han"; // 指向字符数组的指针变量重新赋值,指向此字符串的首地址(省略了字符数组的定义)
printf("%s\n", p); // 此时输出 like han
// p 与 s 的区别:
// 两者同样是表示数组 s 的地址,在表现形式上可以互换,但它们本质不同,p 是地址变量(可以改变指向),而 s 是地址常量(不可改变指向)