指针简介
什么是指针?
从根本上看指针是一个值为内存地址的变量(或数据对象),指针变量的值是地址。
与指针相关的运算符
- & 后接一个变量名时,&给出该变量的地址
- * 后跟一个指针名或者地址时,*给出存储在指针指向地址上的值
声明指针
声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针的操作要求知道操作对象的大小(例如指针的加法、减法等都是以指向对象为准的)。另外,程序需要知道存储在指定地址上的数据类型。
指针的初始化:
//在指针定义处对指针进行初始化操作
#include<stdio.h>
int main(void)
{
int a = 10;
//此处的*p和定义后的*p代表的意义不同
int *p = &a;
}
//先定义指针后初始化
#include<stdio.h>
int main(void)
{
int a = 10;
int *p;
p=&a;
}
指针的基本操作
- 赋值操作:可以将地址赋给指针。例如,用数组名、带地址运算符(&)的变量名、另一个指针进行赋值。注意:地址应该和指针类型兼容。也就是说,不能把double类型的地址赋给指向int的指针。
double i=10;
int *p;
p=&i //避免这样做
- 解引用:*运算符给出指针指向地址上存储的值。注意:不能解引用一个未初始化的指针。创建一个指针,系统只分配了存储指针本身的内存,并未分配存储数据的内存。因此,在使用指针之前,必须先用已分配的地址初始化它。
int i=3,*p;
*p=i;//不要这样做,因为指针p没有进行初始化
- 取址:和所有变量一样,指针变量也有自己的地址和值。对于指针而言,&运算符给出指针本身的地址。
- 指针和整数相加:可以使用+运算符把指针与整数相加,或整数与指针相加。无论哪种情况,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。
- 递增指针:递增指向数组元素的指针可以让指针移动至数组的下一个元素
- 指针减去一个整数:可以使用-运算符从一个指针中减去一个整数,指针必须是第一个运算对象,整数是第二个运算对象。如果相减的结果超过了初始指针所指向数组的范围,计算结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效
- 递减指针
- 指针求差:通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。如果指向两个不同数组的指针进行求差运算可能会得到一个值,或者导致运行时错误。
- 比较:使用关系运算符可以比较两个指针的值,前提是两个指针指向相同的数据类型的对象。
空指针NULL
在C语言中,如果一个指针不指向任何数据,我们称之为空指针,使用NULL表示。因为空指针没有指向任何数据,所以地址就是随机值。一些函数对空指针是很敏感的(如结构体中的strcpy_s()函数),在使用前判断一下是否为空。
指针和数组
为什么要将指针和数据结合使用?
指针能够有效的处理数组,数组表示法其实是在变相的使用指针。
一维数组和指针
数组名是数组首元素的地址,也就是说如果flizny是一个数组,那么下列的语句成立:
flizny == &flizny[0]
指针表示法和数组表示法
在C语言中,a[i]和*(a+i)这两个表达式都是等价的。无论a是数组名还是指针变量,这两个表达式都没有问题。但是,只有当a是指针变量时,才能够使用a++这样的表达式。
保护数组中的数据
防止在使用指针的过程中对数组中的元素进行了更改我们需要使用const来保护数组中的数据不被更改。主要有三种方式来保证相关的数据不被更改。
- const int *p 不改变地址上的值
- int * const p 不改变指针p
- const int * const p 不改变地址上的值,也不改变指针p
二维数组和指针
创建一个二维的数组:
int zippo[4][2]//四行两列的数组
zippo的首元素是一个内含两个int值的数组,数组名zippo是该数组首元素的地址。
#include <stdio.h>
int sum_demo1(int* a, int n);
int sum_demo2(int* start, int* end);
void sum_rows(int ar[][2], int rows);//二维数组行和函数
void sum_cols(int(*p)[4], int rows);//二维数组列和函数
void sum_total(int p[][2], int rows);//二维数组总和函数
int main(void)
{
//创建一维数组
int b[5] = { 1,2,3,4,5 };
int a[] = { 1,2,3,4 }; //省略方框中的数字,让编辑器自动匹配
int sum_b = 0;
int sum_a = 0;
//数组的引用 数组名[下标]、*(b+1)与数组名表达形式相同
for (int i = 0; i < 5; i++)
{
printf("b[%d]=%d\n", i, b[i]);
}
for (int i = 0; i < 5; i++)
{
printf("b=[%d]=%d\n", i, *(b + i));
}
//数组名
printf("数组名表示首字母地址,a=%p\n", a);
printf("数组名的地址一样是首字母地址,&a=%p\n", &a);
//在函数中调用数组
//因为作为实际参数的数组名是一个指针,所以要求形参是一个与之匹配的形参
sum_b = sum_demo1(b, 5);
printf("sum_b=%d\n", sum_b);
sum_a = sum_demo2(a, (a + 4));
printf("sum_a=%d\n", sum_a);
//创建二维数组
int c[3][2] = { {1,2},{3,4},{5,6} };
//数组名
printf("c表示{1,2}地址,c[0]表示{1,2}中1的地址");
printf("c的地址,&c=%p\n", &c);
printf("数组名表示首元素地址即{1,2}地址,c=%p\n", c);
printf("c[0]=%p\n", c[0]);
printf("数组名表示首个整数地址即{1,2}中{1}的地址,c[0][0]=%p\n", &c[0][0]);
//数组的引用
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
printf("c[%d][%d]=%d\n", i, j, c[i][j]);
printf("c[%d][%d]=%d\n", i, j, *(c[i] + j));
printf("c[%d][%d]=%d\n", i, j, *((*(c + i)) + j));
}
}
sum_rows(c, 3);
sum_cols(c, 3);
sum_total(c, 3);
}
//一维数组相加函数
int sum_demo1(int* a, int n)
{
int i;
int total = 0;
for (i = 0; i < n; i++)
{
//在函数中使用数组表示法,使得意图更加明显
total += a[i];
}
return total;
}
//使用指针算法
int sum_demo2(int* start, int* end)
{
int total = 0;
while (start < end)
{
total += *start;
start++;
}
return total;
}
//二维数组行和函数
//第一个括号是空的,空的括号表示ar是一个指针
void sum_rows(int ar[][2], int rows)
{
int rows_total = 0;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 2; j++)
{
rows_total += ar[i][j];
}
printf("rows_total=%d\n", rows_total);
rows_total = 0;
}
}
//列和函数
//int(*p)[4]定义一个p指针,指向一个含有四个int类型值的一维数组
void sum_cols(int(*p)[2], int rows)
{
int cols_total = 0;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < rows; j++)
{
cols_total += p[j][i];
}
printf("cols_total=%d\n", cols_total);
cols_total = 0;
}
}
//二维数组总和函数
void sum_total(int p[][2], int rows)
{
int total = 0;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 2; j++)
{
total += *(*(p + i) + j);
}
}
printf("total=%d\n", total);
}
字符串
函数名 | 作用 | 缺点 |
---|---|---|
printf() | 将字符串的地址作为参数,依次打印直到结束。 | |
puts() | 显示字符串,并在末尾添加换行符。 | |
gets() | 读取整行,直至遇到换行符,然后丢弃换行符,存储其他字符,并在这些字符的末尾添加一个空字符。 | gets函数输入参数只有words不能检测数组是否能装下输入行。若字符过长将导致缓冲区溢出。 |
fgets(words,stlen,stdin) | 大多用于处理文件输入,第二个参数用于指定输入字符的最大数量。如果该参数的值是n那么读入n-1个字符,或者读到遇到的第一个换行符为止。当输入行不溢出是,fgets函数将换行符放在最后。 第三个参数用于说明读入的文件,如果读入从键盘输入的数据,则以stdin作为参数 | |
fputs(words,stdout) | 第二个参数指定它要写入的文件。 | |
scanf() | 以一个空白字符(空行、空格、制表符或换行符)作为字符串的结尾 | 不安全可能会产生溢出的情况。 |
scanf_s("%s",a,30) scanf_s(%d,&a) | scanf()函数的改进,给定需要传入的长度,防止溢出,在传入字符时体现出其安全性。 | |
string.h系列函数 | ||
strlen() | 统计字符串的长度 | |
strcat(str1,str2) | 拼接字符串,传入两个字符串,将str2副本加到str1后面,并返回str1.str1被改变,str2不变 | str1的大小可能不足以容纳str1和str2产生溢出 |
strncat(str1,str2,max) | 上述函数的改进,第三个参数规定str2最多多少个字符可以加到str1 | |
strcmp() | 检测字符串是否相同,如果两个字符串参数相同,该函数返回0. | |
strncmp(str1,str2,num) | 第三个参数限定函数只查找几个字符。 | |
strcpy(copy,orig) | 拷贝整个字符串 | |
strncpy(copy,orig,num) | 指定最大拷贝字符数 |
函数
#include<stdio.h>
int max(int, int);
int min(int, int);
int add(int, int);
int main(void)
{
int (*p)();//指向函数的指针
int a, b, c;
p = max;
scanf_s("%d %d", &a, &b);
c = (*p)(a, b);
printf("a=%d,b=%d,max=%d\n", a, b, c);
//用指向函数的指针作为函数参数
//这样在函数中就可以调用其他函数了
printf("max=");
progress(a, b, max);
printf("min=");
progress(a, b, min);
printf("sum=");
progress(a, b, add);
}
int progress(int x, int y, int(*p)())
{
int i = 0;
if (p == max)
{
i = (*p)(x, y);
printf("%d\n",i);
}
else if (p == min)
{
i = (*p)(x, y);
printf("%d\n", i);
}
else if (p = add)
{
i = (*p)(x, y);
printf("%d\n", i);
}
}
int max(int x, int y)
{
int i = 0;
if (x > y)
{
i = x;
}
else
{
i = y;
}
return i;
}
int min(int x, int y)
{
int i = 0;
if (x > y)
{
i = y;
}
else
{
i = x;
}
return i;
}
int add(int x, int y)
{
int i = 0;
i = x + y;
return i;
}
结构体
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAXTITLE 41
#define MAXAUTHOR 31
#define SLEN 81
//声明结构体
struct book
{
char title[MAXTITLE];
char author[MAXAUTHOR];
float value;
};
struct off
{
float first_off;
float seconf_off;
};
struct buy
{
struct off buyit;
char name[30];
float value;
};
struct namect
{
char* fname;
char* lname;
int letters;
};
char* s_gets(char* st, int n);
void getinfo(struct namect* pst);
void makeinfo(struct namect* pst);
void showinfo(struct namect* pst);
void cleanup(struct namect* pst);
int main(void)
{
//定义结构变量、初始化结构
struct book book1 = { "The C PRIMER","Cking",21.1 };
//访问结构成员
printf("book1.title=%s\n", book1.title);
printf("book1.author=%s\n", book1.author);
printf("book1.value=%f\n", book1.value);
//结构数组
struct book library[2] = { {"The C PRIMER","Cking",21.1},{"The Lunix","C_king",21.1} };
printf("library[0].title=%s\n", library[0].title);
printf("library[1].title=%s\n", library[1].title);
//嵌套结构的使用
struct buy nike_buy = { {0.8,0.7},"nike",500 };
printf("初始价格为:%f\n", nike_buy.value);
printf("折上折后的价格为:%lf", nike_buy.buyit.first_off *nike_buy.buyit.seconf_off*nike_buy.value);
//指向结构的指针
struct book* p;
struct book* q;
p = &book1;
printf("book1.title=%s\n", (*p).title);
printf("book1.author=%s\n", (*p).author);
printf("book1.value=%f\n", (*p).value);
q = library;
printf("library[0] address=%p,library[2] address=%p\n", &library[0], &library[1]);
printf("q:%p,q+1:%p\n", q, q + 1);
printf("library[0].title=%s\n",(*q).title );
printf("library[1].title=%s\n",(*(q+1)).title );
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
cleanup(&person);
return 0;
}
void getinfo(struct namect* pst)
{
char temp[SLEN];
printf("Please enter your first name.\n");
s_gets(temp, SLEN);
// 分配内存
(*pst).fname = (char*)malloc(strlen(temp)*sizeof(char)+1);
//pst->fname= (char*)malloc(strlen(temp) + 1);
//将数据写入分配好的内存中
if ((*pst).fname == NULL)
return EOF;
else
strcpy_s((*pst).fname, strlen(temp)+1, temp);
printf("please enter your last name.\n");
s_gets(temp, SLEN);
(*pst).lname = (char*)malloc(strlen(temp)*sizeof(char) + 1);
//pst->fname= (char*)malloc(strlen(temp) + 1);
//将数据写入分配好的内存中
strcpy_s((*pst).lname, strlen(temp)+1, temp);
}
void makeinfo(struct namect* pst)
{
(*pst).letters = strlen((*pst).fname) + strlen((*pst).lname);
}
void showinfo(struct namect* pst)
{
printf("%s %s,your name contins %d letters.\n", (*pst).fname, (*pst).lname, (*pst).letters);
}
void cleanup(struct namect* pst)
{
free((*pst).fname);
free((*pst).lname);
}
char* s_gets(char* st, int n)
{
char* ret_val;
char* find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n');
if (find)
{
*find = '\0';
}
else
{
while (getchar() != '\n')
continue;
}
}
return ret_val;
}