第七章 指针的概念
如何访问、如何引用、如何存储????
问题:
1、如何访问变量?
通过变量名称来访问变量
2、如何访问指针变量?
通过指针变量名
问题一:如何通过指针访问值?
#include <stdio.h>
int main()
{
int number = 15;
int *pointer = &number;
int result = 0;
printf("%d\n",*pointer);
result = *pointer + 5;
printf("%d\n",result);
return 0;
}
7.1 指针初探
指针是C语言中最强的工具
int number = 5;
这条语句会分配一块内存来存储一个整数,使用变量number的名称可以访问这个整数,
值5存储在这个区域中。
计算机用一个地址引用这个区域。
存储地址的变量称为指针(pointers),存储在指针中的地址通常是另一个变量,
指针的工作原理
int number = 99;
int *pnumber = &number;
指针pnumber含有另一个变量number的地址,变量number是一个值为99的整数变量。
指针只是一个存储内存位置的地址。
存储在pnumber中的地址是number第一个字节的地址。
编译器需要知道指针 指向的变量类型。
char 类型值的指针指向占有一个字节的值
而long类型值的指针指向占有4个字节的值
- 给定类型的指针写成type*,其中type 是任意给定的类型。
类型名void表示没有指定类型,所以void类型的指针可以包含任意类型的数据项地址。类型void常常用作参数类型,或以独立于类型的方式处理数据的函数的返回值类型。任意类型的指针都可以传送为void*类型的值,在使用它是,再将其转换为合适的类型。
7.1.1 声明指针
(1)声明指针变量时,指针变量前一定要有“ * ”符号
(2)定义指针时,要加上类型标示符。
类型 *指针变量名
声明一个指向int类型的变量指针
int *pnumber; &&&& int* pnumber
以上两条语句完全相同,但使用时最好始终使用其中一种,没有初始化的指针是非常危险的,所以应总是在声明指针时对他进行初始化。
初始化pnumber,使他不指向任何对象。
int *pnumber = NULL;
寻址运算符&:获取变量的地址。
取消引用运算符*:获取指针指向的地址中的内容,并对该地址中的内容进行赋值操作。
p = &a;
NULL是在标准库中定义的一个常量,对于指针它表示0。NULL是一个不指向任何内存位置的值。
NULL在头文件< stddef.h> 、<stdlib.h>、<stdio.h>、<string.h>、< time.h>、<wchar.h>和<locale.h>中定义
只要编译器不能识别NULL,就应在源文件中包含<stddef.h>头文件
如果用已声明的变量地址初始化pointer变量,可以使用寻址运算符。如下所例:
int number = 99;
int *pnumber = &number;
pnumber的初值是number变量的地址。注意,number的声明必须在pnumber的生命之前。
用相同的语句声明一般的变量和指针
double value, *pVal, fnum;
这条语句声明了两个变量,以及一个指向double的变量pVal
int *p , q;
声明一个指针和一个变量
7.1.2 通过指针访问值
使用间接运算符*可以访问指针所指的变量值
取消引用运算符(dereferencing operator )取消对指针的引用。
int number = 15;
int *pointer = &number;
int result = 0;
7.1.3 使用指针
星号*表示访问指针变量所指向的内容
在算术语句中使用取消引用的指针
*pnumber += 25;
将变量pnumber所指向的地址中的值增加25
int value = 999;
pnumber = &value;
指针可以包含同一类型的任意变量的地址,所以使用一个指针变量可以改变其他很多变量的值,只要它们的类型和指针相同。
运算符++和一元运算符* (&)的优先级相同,且都是从右往左计算的。
pvalue 和 value 是相同的所以用任何一个都可以
7.14 指向常量的指针
常量指针与指针常量的指针的区别是
-
常量指针是指在指针中存储的地址不发生改变。
指向常量的指针是指针指向的值不发生改变。
使用const关键字声明指针时,该指针指向的值不能改变。
long value = 9999L;
const long *pvalue = &value;
指针本身不是常量,所以可以改变指针指向的值
long number = 8888L;
pvalue = &number;
把变量number的地址赋值给指针变量pvalue,指针变量是一个含有地址的变量,所以可以使用指针变量名作为参数即
(&value 等价于pvalue)
指针是另外一个变量的地址
- 指针可以改变指针中储存的地址,但不能改变指针的指向的值
7.1.5 常量指针
什么是常量指针(常量指针的概念是什么)?
- 常量指针是指在指针中存储的地址不发生改变。
下面的语句可以是指针总是指向相同的对象:
int count = 43;
int *const pcount = &count;
可以创建一个常量指针,它指向一个常量值:
int item = 25;
const int *const pitem = &item;
7.1.6 指针的命名
最好将P作为指针名的第一个字母
7.2 数组和指针
-
数组是相同类型的(对象)元素集合,在内存中占据着一块连续的存储空间,每一个元素都有一个确定的地址值。因此可以利用指针对数组中的每一个元素进行操作。
-
指针是一个变量,它的值是给定类型的另一个变量或常量的地址。
数组名可表示数组的首地址,即数组第一个元素所在的位置
数组名等于数组第一个字节的地址,&multiple[0] 等于数组第一个元素的第一个字节
有三种方式可以实现对数组元素的访问
(1)通过下标访问 x[i](2)通过地址访问 (3)通过指针访问
用scanf_s输入一个字符,可以用一下语句
char single = 0;
scanf_s("%c", &single, sizeof(single));
如果读入字符串,可以编写如下代码
char multiple[10];
scanf_s("%s", multiple, sizeof(multiple));
数组和指针有一个重要的区别:可以改变指针包含的地址,但不能改变数组名称引用的地址。
编译器知道,给地址值加1时,就表示要访问该类型的的下一个变量,这就是为什么声明一个指针时,必须要指定的该指针指向的变量类型。
数组名称是一个固定的地址,而不是一个指针,可以在表达式中使用数组名及其引用的地址,但不能修改它。
7.3 多维数组
board 是char型二维数组的地址,board[0]是char型1⃣️维子数组的地址,它是board的一个子数组,&board[0][0]是char型数组元素的地址。
如果使用board获取第一个元素的值,就需要使用两个间接运算符,
如果只使用一个*,只会得到子数组的第一个元素。
board引用子数组中第一个元素的地址
board[0]board[0]board[0]引用对应子数组中第一个元素的地址。
用两个索引值访问存储在数组元素中的值。
7.3.1 多维数组和指针
7.3.2 访问数组元素
问题:如何实现(board)
访问数组元素的指针表达式
board | 0 | 1 | 2 |
---|---|---|---|
0 | board[0][0] | board[0][1] | board[0][2] |
0 | * board[0] | *(board[0] + 1) | * ( board[0] +2) |
0 | **board | (board+1) | (board + 2 |
1 | board[1][0] | board[1][1] | board[1][2] |
1 | * board[1 ] | *(board[1 ] + 1) | * ( board[1] +2) |
1 | (board+3) | (board +4) | (board+5) |
2 | board[2][0] | board[2][1] | board[2][2] |
2 | *board[2] | (board[2]+1) | (board[2]+2) |
board | 0 | 1 | 2 |
---|---|---|---|
0 | board[0][0] | board[0][1] | board[0][2] |
1 | board[1][0] | board[1][1] | board[1][2] |
2 | board[2][0] | board[2][1] | board[2][2] |
board | 0 | 1 | 2 |
---|---|---|---|
0 | * board[0] | *(board[0] + 1) | * ( board[0] +2) |
1 | * board[1] | *(board[1] + 1) | * ( board[1] +2) |
2 | *board[2] | (board[2]+1) | (board[2]+2) |
board | 0 | 1 | 2 |
---|---|---|---|
0 | **board | * ( * board+1) | (board + 2 |
1 | (board+3) | (board +4) | (board+5) |
7.4 内存的使用
指针是一个强大的编程工具
c语言还有一个功能:动态内存分配
在程序的执行期间分配内存时,内存区域中的这个空间称为堆(heap),还有另一个内存区域,称为堆栈(stack),其中的空间分配给函数的参数和本地变量。在执行完该函数后,存储参数和本地变量的内存空间就会释放。
堆中的内存是由程序猿控制的
7.4.1 动态内存分配:malloc()函数
动态内存分配(dynamic memory allocation)
在运行时分配内存的最简单的标准库函数是malloc()函数,
使用malloc函数需要指定要分配分内存字节数作为参数。malloc函数返回所分配内存的第一个字节的地址。
int *pNumber = (int*)malloc(100);
int *pNumber = (int*)malloc(25*sizeof(int));
类型转换(int*)将函数返回的地址转换成int类型的指针
int *pNumber = (int*)malloc(25*sizeof(int));
if(pNumber)
{
//code to deal with memory allocation failure
}
7.4.2释放动态分配的内存
动态分配了一些内存时,没有保留对它们的引用,就会出现内存泄露,此时无法释放内存。
必须能访问引用内存块的地址
free(pNumber)
pNumber = NULL;
free()函数的形参是void*类型,所有的指针类型都可以自动的转换为这个类型,所以可以把任意类型的指针作为参数传送给free()函数
在指针指向的内存释放后,应总是把指针设置为NULL;
在释放内存后,应总是把指向堆内存的指针设置为NULL,这样就不会使用不再可用的内存了使用不再可用的内存总是很危险的。
malloc函数的参数是size_t类型
如果size_t对应4字节的无符号的整数,则一次至多可以分配4294967295个字节
7.4.3 用calloc函数分配内存
它把内存分配为给定大小的数组,
它初始化了所分配的内存,所有的位都是0
数组的元素个数和数组元素占用的字节数,这两个参数的类型都是size_t,
calloc函数不知道数组的类型,所以分配的内存区域地址返回为void*类型。
int *pNumber =(int*)calloc(75,sizeof(int));
如果不能分配所请求的内存,返回值就是NULL;
int *pNumber = calloc(75,sizeof(int));
pPrimes = calloc((size_t)total,sizeof(unsigned long long));
if(pPrimes == NULL)
{
printf(" Not enough memory.It's the end I am afraid.\n");
return 1;
}
7.4.4 扩展动态分配的内存
realloc函数需要两个参数:包含地址的指针。要分配的新内存的字节数
如果realloc的第一个参数是NULL,就分配第二个参数制定的新内存。如果第一个参数不是NULL,但不指向以前分配的内存,或者指向已释放的内存,结果就不确定了。
7.5使用指针处理字符串
存储字符和引用字符串
- 1、char类型的数组元素存储字符串
- 2、char类型的指针变量引用字符串
- 3、声明指针变量时只是创建了字符串变量并没有指定一个存储字符串的地方
要存储字符串,需要分配一些内存,指针变量中存储其地址。
因此使用指针变量存储字符串地址是用动态内存分配功能非常有效。
指针首先是一个变量,指针只是存储了另一个内存位置的地址的变量。
- 声明一个char类型的指针变量 即只创建了指针,没有指定一个存储字符串的地方,
char *pString = NULL;
- 指针只是一个存储另一个内存位置的地址的变量。
const size_t BUF_SIZE = 100;
char buffer[BUF_SIZE]
scanf_s("%s", buffer, BUF_SIZE);
size_t length = strnlen_s(buffer, BUF_SIZE) + 1;
char *pString = malloc(length);
if(!pString)
{
printf("memory allocation failed.\n");
return 1;
}
strcpy_s(pString,length,buffer);
printf(" %s ", pString);
free(pString);
pString = NULL;
这段代码把一个字符串读入一个char数组中,给读入的字符串分配堆上的内存,在将字符串复制到pString引用的内存中。就允许重用buffer来读取更多的数据。
7.5.1使用指针数组
- 创建一个指针数组,存储字符串的位置。
char *pS[10] = { NULL };
这个语句声明一个数组pS,数组包含10个char*类型的元素。
- 数组中的每一个元素都可以存储字符串的地址。
- 初始化列表中只有一个NULL,NULL将任意大小的指针数组中的所有元素都初始化为NULL。
#define STR_COUNT 10
const size_t BUF_SIZE = 100;
char buffer[BUF_SIZE];
char *pS[STR_COUNT] = { NULL };
size_t str_size = 0;
for(size_t i = 0; i < STR_COUNT; ++i)
{
scanf_s("%s",buffer, BUF_SIZE);
str_size = strnlen_s(buffer, BUF_SIZE) + 1;
pS[i] = malloc(str_size);
if(!pS[i] return 1;
strcpy_s(pS[i],str_size,buffer);
}
for(size_t i =0; i < STR_COUNT ; ++i)
{
free(pS[i]);
pS[i] = NULL;
}
- 数组记号
使用指针名和索引值共同构成
数组记号来存储相同类型的几个数据类型 - 指针数组是指针记号
指针名和索引值构成
数组记号和指针记号的区别是什么呢?
指向一块推内存的指针变量不仅可以用指针记号还可以用数组记号
int count = 100;
double *data = calloc(count,sizeof(count));
for(int i = 0; i<count; ++i)
{
data[i] = double(i+1)*(i+1);
}
- pS数组的每个元素都保存从键盘读取的一个字符串的地址
问题:
1、字符串的存储与引用方式有几种类型。
2、 如何处理任意长度的字符串
3、如果不知道要输入多少个字符串,该怎么办?