指针一定要从地址的角度理解,就是保存地址的变量。32bit系统不管什么数据类型都是占 4 字节,64bit系统就是占 8 字节
指针常量:指向不能动,但是指向的内容可以动
指针常量存的其实是一个地址。指针常量的定义方法:
int i = 1;
int *const p = &i; // p 是指针常量
指针常量也可以叫地址常量,说明指针常量里面存的是地址, 且是不可改变的,也就是指向不变。但是地址里面的值是可以改的。
//64bit 系统上运行的结果
#include <iostream>
using namespace std;
// 指针常量, int *const p。指针常量占8个字节(64 bit系统),所以指针常量就是个地址(也可叫地址常量)
int main() {
int i = 1;
int a = 2;
cout << &i << endl; // 变量 i 的地址
int *const p = &i; // 定义一个指针常量 p,指针常量是8个字节的地址,且地址不可改变
i = 2; // 指针常量地址所指向的值可以改变
cout << "*p = " << *p << endl; // 改变地址指向的值
// p = &a; // 指针常量p指向固定的地址,更改指针常量地址会报错
//p++; // 更改指针常量 p 里的地址会报错,即指向不能动
*p = 5; // 不能改变 p 里面所存的地址,但是可以通过 *p 直接对地址里存的值进行更改,即指向的内容可动
cout << p << endl; //
cout << sizeof(int *const) << endl; // 指针常量占 8 字节(64 bit),32 bit 占 4 字节
cout << sizeof(const int) << endl; // 常量指针占 4 字节(64bit and 32 bit)
cout << sizeof(p) << endl; // p 是指针常量,占 8 字节(64bit)
cout << sizeof(*p) << endl; // *p 是整型变量,占 4 字节(64 bit and 32 bit)
}
>>>
0x78fe1c
*p = 2
0x78fe1c
8
4
8
4
指针常量示意图(红色代表不可更改):
常量指针:指向可以动,但是指向的内容不能动
指针可以认为是保存地址的一个变量(地址变量)。常量指针的定义如下:
int i = 1;
const int *p = &i; // 定义常量指针 p
常量指针 p 里面也是存地址的,
#include <iostream>
using namespace std;
// 常量指针, const int *p。指针常量占 8 个字节(64 bit系统),所以存的也是地址
int main() {
int i = 1;
int a = 2;
cout << &i << endl; // 变量 i 的地址
const int *p = &i; // 定义一个常量指针 p,常量指针 p 是 8 个字节的地址
cout << "*p = " << *p << endl; // 地址指向的值
cout << p << endl; // 常量指针的地址
// *p = 5; // 企图通过常量指针来更改 i 对应的值,会报错
i = 3; // 虽然不能通过常量指针来更改 i 对应的值,但是可以改变 i 本身
cout << "*p = " << *p << endl; // 改变地址指向的值
p++; // 改变常量指针里的地址值
cout << p << endl; // 常量指针的地址
cout << sizeof(const int*) << endl; // 常量指针占 8 字节(64 bit),32 bit 占 4 字节
cout << sizeof(int *const) << endl; // 指针常量占 8 字节(64 bit),32 bit 占 4 字节
>>>
0x78fe1c
*p = 1
0x78fe1c
*p = 3
0x78fe20
8
常量指针的示意图:
数组名是一个常量指针
对于整形数组来说,因为数组名是一个固定的地址(数组的首地址),所以就是指向不能动,但是指向的内容可移动,即可以通过指针常量改变数组内容。
(1)int
类型数组的例子:
#include <iostream>
using namespace std;
int main() {
int a[3] = {1, 2, 3}; // 定义数组并初始化
//int a[3]; // 定义数组
//a = {1, 2, 3}; // 定义数组后,如果直接用 = 赋值会出错,因为数组名是常量地址(常量指针),不能被赋值,c中只有变量才能被赋值
//a[0] = 1; // 只声明了数组之后,就只能通过这种方式初始化数组
//a[1] = 2;
//a[2] = 3;
cout << a << endl;
cout << &a[0] << endl;
cout << "a[0] = " << a[0] << endl;
*a = 5; // 通过数组名(常量指针)改变值
cout << "a[0] = " << a[0] << endl;
cout << sizeof(a) << endl;
}
>>>
0x78fe14
0x78fe14
a[0] = 1
a[0] = 5
12
(2)char
类型的数组例子
初始化:char
类型数组初始化除了可以和int
类型初始化一样以外,还可以用strcpy()
函数来初始化
#include <iostream>
using namespace std;
int main() {
char a[5]; //
char b[] = "yang"; // 这是一个字符串,等价于 char b[] = {'y','a','n','g','\0'}; 效果一样
//char b[] = {'y', 'a', 'n', 'g', '\0'};
strcpy(a, b); // 用 strcpy() 初始化字符数组,a的长度最好大于等于b的长度,不然会出现警告, 注意strcpy()没有返回值
// strcpy(a, "yang"); // 用 strcpy() 初始化字符数组
cout << "a = " << a << endl; // 字符串名字并不是常量指针,输出 yang
cout << "a[0] = " << a[0] << endl;
cout << "&a = " << &a << endl; // 字符串名字并不是常量指针,输出字符串的首地址
cout << "&a[0] = " << &a[0] << endl; // 并没有输出字符串的首地址,输出yang
cout << "&a[1] = " << &a[0] << endl; // 输出 ang
cout << sizeof(a) << endl;
}
>>>
a = yang
a[0] = y
&a = 0x78fe16
&a[0] = yang
&a[1] = ang
5
并没有字符数组这个概念,字符数组就是字符串,在c++编译器内,char b[] = {'y', 'a', 'n', 'g', '\0'};
和char b[] = {'y', 'a', 'n', 'g'};
会被默认为字符串"yang"
,系统会自动增加'\0'
。字符数组的名字并不是首字母的地址,而是整个字符串。。
数组和字符串的关系:字符串可以看成是char型的数组。
字符串就是数组后面加了一个\0
结尾字符,所以字符串的性质和数组性质差不多,只是字符串长度比数组+1。字符串本身就是数组,数组本身就是指针,所以字符串也是指针,但是字符并不是指针。。但是字符串名字不是一个常量指针,是一个字符串类型。char型数组赋值可以用strcpy()
函数,
用string
定义字符串,其实是用了容器,本质上的字符串就是个char
型的数组,在c++里,char型数组会被认为是字符串。
char b[] = "yang"; // 这是一个字符串,等价于 char b[] = {'y','a','n','g','\0'}; 效果一样
char b[] = {'y', 'a', 'n', 'g', '\0'};
// 这两个定义字符串的方法是一样的
字符串的一些函数:
strcpy(s1, s2)
; 复制字符串 s2 到字符串 s1。
strcat(s1, s2)
; 连接字符串 s2 到字符串 s1 的末尾。连接字符串也可以用 + 号,即两个字符串可以相加。
strlen(s1)
;返回字符串 s1 的长度。
strcmp(s1, s2)
; 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。
strchr(s1, ch)
; 返回一个指针,指向字符串 s1 中**字符 ch 第一次出现的位置。
strstr(s1, s2)
; 返回一个指针,指向字符串 s1 中字符串 s2 **第一次出现的位置。
c++指针数组和数组指针
(1)c++指针数组:它是一个数组,数组里面存储的是一些指向 int 或 char 或其他数据类型的指针。指针数组的声明如下: int *ptr[MAX]
( []
优先级比*
高,所以ptr
和[]
先结合成一个数组,int *
只不过是修饰ptr[MAX]
数组的,所以这是指针数组);ptr
是一个数组名, 在这里ptr
中的每个元素,都是一个指向 int 值的指针。在一个32位操作系统中,一个指针变量占的内存大小固定为4字节,指针数组的内存大小看数组的大小。
(2)c++数组指针:首先它是一个指针,指向一个数组的入口地址,在一个32位操作系统中,它只占4字节。数组指针的声明如下: int (*ptr)[10]
;这是一个指针,ptr
是指针变量名,指向一个有10个元素的整形数组的入口(这个数组是匿名的),使用时类似函数指针,先给该指针赋一个数组的入口地址,才能找到该数组。
指针函数和函数指针
(1)指针函数:返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。声明如下:int *fun(int x,int y);
这个函数就是一个指针函数。其返回值是一个 int 类型的指针,返回值是一个地址。指针函数可以定义为 void 类型的。
#include <iostream>
using namespace std;
int p = 0; // 只能输出全局变量的地址
int *add(int a, int b) {
p = a + b;
int *m = &p; // 在调用指针函数时, 需要一个同类型的指针来接收其函数的返回值
return m;
}
int main() {
int a = 2;
int b = 4;
int *p = add(a, b);
cout << *p << endl;
return 0;
}
(2)函数指针:声明如下:int (*fun)(int x,int y);
,其中函数指针 (*fun)
本质是一个指针变量,该指针指向某个函数的入口,即函数的地址。总结来说,函数指针就是指向函数的指针,或者说保存函数入口地址的指针。声明如下:int (*fun)(int x,int y);
函数指针是需要把一个函数的地址赋值给它。函数指针指向的函数必须有返回值,否则它将指向一个空地址,这是指针使用时候所不允许的情况。
#include <iostream>
using namespace std;
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
int (*fun)(int x, int y); // 声明函数指针,两个函数只需要声明一次即可
int main(int argc, char *argv[]) {
//第一种写法
fun = &add; // 把add函数的地址赋给fun,也就是将函数的入口给fun。取址运算符 & 不是必须的,因为一个函数标识符(function name)就表示了它的地址
cout << "(*fun)(1, 2) = " << (*fun)(1, 2) << endl;
//第二种写法
fun = ⊂ // 把sub函数的地址赋给fun,也就是将sub函数的入口给fun
cout << "fun(1, 2) = " << (*fun)(1, 2) << endl;
return 0;
}
main(int argc, char *argv[])
中第一个参数,int型的argc
,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,第二个参数,char*
型的argv[]
,为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下: argv[0]指向程序运行的全路径名, argv[1]指向在DOS命令行中执行程序名后的第一个字符串, argv[2]指向执行程序名后的第二个字符串, argv[3]指向执行程序名后的第三个字符串, argv[argc]为NULL .
c++引用
(1)引用其实就是给变量起了个别名,实质上是个指针常量。在语法上引用没有独立空间,和被引用的实体公用以块空间。其实,引用是有空间的,因为它是用指针来实现的。
#include <iostream>
using namespace std;
int main() { // 引用有指针常量的性质,既可以通过引用b来更改a的值,也可以通过a本身来更改
int a = 10;
int &b = a;
a = 20;
b = 30; //
cout << a << endl;
cout << b << endl;
cout << &a << endl; //
cout << &b << endl; // 与 a 公用一块地址
int &c = a; // 一个变量可以有多个引用
c = 60;
cout << c << endl; // 多个引用一个更改,其他的都更改
cout << a << endl; // 语法上没有独立空间,和引用的实体共用一块空间。
cout << b << endl;
return 0;
}
>>>
30
30
0x78fe1c
0x78fe1c
60
60
60
如果变量时const类型,则引用类型也要是const类型。如下:
#include <iostream>
using namespace std;
int main() { // 引用有指针常量的性质,既可以通过引用b来更改a的值,也可以通过a本身来更改
const int a = 10;
const int &b = a;
(2)引用的作用
- 引用做函数的参数:传引用可以提高效率。传引用可以减少深拷贝。
引用做参数不需要拷贝,因为形参是实参的别名。可以提高效率。
int swap(&a, &b); // 调用时相当于执行 &a = c, &b = d,即形参是实参的别名
swap(c, d); //
- 引用做返回值:减少拷贝,提高效率
这个程序 count() 函数输出的 n 需要拷贝给临时变量,然后临时变量在拷贝给 ret, 需要拷贝两次。
引用做 Count() 函数的返回值只需要一次拷贝。
(3)引用和指针的区别:引用的本质是个指针常量,但是和一般的指针有一些不一样。