网站建设建设公司资质要求,惠州做棋牌网站建设,凡客网站规划与建设ppt,网页前端开发工资多少因为前期在学驱动#xff0c;所以花了一天时间借鉴了别的资料#xff0c;把本科学的C语言捡起来。
指针的基本概念
堆栈有栈顶指针#xff0c;队列有头指针和尾指针#xff0c;这些概念中的指针本质上是一个整数#xff0c;是数组的索引#xff0c;通过指针…因为前期在学驱动所以花了一天时间借鉴了别的资料把本科学的C语言捡起来。
指针的基本概念
堆栈有栈顶指针队列有头指针和尾指针这些概念中的指针本质上是一个整数是数组的索引通过指针访问数组中的某个元素经过学习我们在间接寻址那里看到了另一个指针的概念把一个变量所在的内存单元的地址保存在另外一个内存单元中保存地址的这个内存单元称为指针通过指针和间接寻址访问变量这种指针在C语言中可以用一个指针类型的变量表示例如某程序中定义了以下全局变量:
int *pii;表示定义一个指向int型的指针变量pi,并用i的地址来初始化pi.
int i;
int *pi i;
char c;
char *pc c;注意 后面两行代码定义了一个字符型变量c和一个指向c的字符型指针pc,注意pi和pc虽然是不同类型的指针变量但它们的内存单元都占4个字节因为要保存32位的虚拟地址同理在64位平台上指针变量都占8个字节。
pi是int型的pc是char型的pipc;这样赋值就是错误的。但是可以先强制类型转换然后赋值:
pi (int *)pc;但是 ** 现在pi指向的地址和pc一样但是通过pc只能访问到一个字节而通过pi可以访问到4个字节后3个字节已经不属于变量c了除非你很确定变量c的一个字节和后面3个字节组合而成的int值是有意义的否则就不应该给pi这么赋值。因此使用指针要特别小心很容易将指针指向错误的地址。 ** PI的地址和PC的地址一样了这样就导致了可以访问的只有一个字节的地址了。原来是4字节。
为什么会出现野指针
因为堆栈上分配变量的地址是随机的也就是指针变量P所指向的内存地址也是不确定的。所以这种指向不确定地址的指针成为 “ 野指针 ”为了避免野指针在定义指针变量时就应该给它明确的初值或者把它初始化位NULL
int main(void)
{int *p NULL;...*p 0;...
}空指针
把地址0转换成指针类型。 它的特殊之处在于操作系统不会把任何数据保存在地址0及其附近也不会把地址0-0xfff的页面映射到物理内存所以任何对地址0的访问都会立刻导致段出错。*p0;会导致段错误就像放在眼前的炸弹一样很容易被找到。
void* 指针
ANSI在将C语言标准化时引入了void类型void指针与其他类型的指针之间可以隐式转换而不必用类型转换运算符。注意只能定义void指针而不能定义void型的变量因为void指针和别的指针一样都占4个字节而如果定义void型变量编译器不知道该分配几个字节给变量。
void func(void *pv)
{/* *pv A is illegal */char *pchar pv;*pchar A;
}
int main(void)
{char c;func(c);printf(%c\n, c);指针类型的参数和返回值
#include stdio.h
int *swap(int *px, int *py)
{int temp;temp *px;*px *py;*py temp;return px;
}
int main(void)
{int i 10, j 20;int *p swap(i, j);printf(now i%d j%d *p%d\n, i, j, *p);return 0;
}通过地址访问到内部变量将内部变量进行交换。
指针和数组
int a[10];
int *pa a[0];
pa;在函数原型中如果参数是数组则等价于参数是指针的形式例如:
void func(int a[10])
{...
}等价于
void func(int *a)
{...
}等价于
void func(int a[])
{...
}指针与const 限定符
如下
const int *a;
int const *a;a是一个指向const int型的指针a所指向的内存中的内容是不可改变的所以(*a)是不允许的但a可以改写所以a是允许的。 2. 如下
int * const a;a是一个指向int型的const指针*a是可以改写的但a不允许改写。 3.
int const* const a;a是一个指向const int型的const指针因此*a和a都不允许改写。 指向非const变量的指针或者非const变量的地址可以传给指向const变量的指针编译器可以做隐式类型转换例如:
char c a;
const char *pc c;但是指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针以免投过后者意外改写了前者所指向的内存单元例如对下面的代码编译器会报警告:
const char c a;
char *pc c;即使不用const限定符也能写出正确的程序但良好的编程习惯应该尽可能多的使用const,因为:
1.const给读代码的人传达非常有用的信息。比如一个函数的参数是const char *,你在调用这个函数时就可以放心地传给它char *或const char *指针而不必担心指针所指的内存单元被改写。
2.尽可能多地使用const限定符把不该变的都声明成只读这样可以依靠编译器检查程序中的Bug,防止意外改写数据。
3.const对编译器优化是一个有用的提示编译器也许会把const变量优化成常量。
指针与结构体
首先定义一个结构体类型然后定义这种类型的变量和指针:
struct unit {char c;int num;
};
struct unit u;
struct unit *p u;要通过指针p访问结构体成员可以写成(*p).c和(*p).num,为了书写方便C语言提供了-运算符可以写成p-c和p-num。
指向指针的指针与指针数组
也就是二级指针
int i;
int *pi i;
int **ppi pi;我们知道main函数的标准原型应该是int main(int argc,char *argv[]);argc是命令行参数的个数而argv是一个指向指针的指针为什么不是指针数组?因为前面讲过函数原型中的[]表示指针而不表示数组等价于char **argv.那为什么要写成char *argv[]而不写成char **argv?这样写给读代码的人提供了有用的信息argv不是指向单个指针而是指向一个指针数组的首元素。数组中每个元素都是char *指针指向一个命令行参数字符串。
打开命令行参数
#include stdio.h
int main(int argc, char *argv[])
{int i;for(i 0; i argc; i)printf(argv[%d]%s\n, i, argv[i]);return 0;
}编译
$ gcc main.c
$ ./a.out a b c
argv[0]./a.out
argv[1]a
argv[2]b
argv[3]c
$ ln -s a.out printargv
$ ./printargv d e
argv[0]./printargv
argv[1]d
argv[2]e由于argv[4]是NULL,我们也可以这样循环遍历argv:
for(i0; argv[i] ! NULL; i)NULL标识着argv的结尾这个循环碰到NULL就结束因而不会访问越界这种用法很形象地称为Sentinel,NULL就像一个哨兵守卫着数组的边界。
指向数组的指针与多维数组
指向数字的指针其实前面讲过的数组指针。
int (*a)[10];int (*a)[10]可以拆分成
typedef int t[10];
t *a;t代表由10个int组成的数组类型a则是指向这种类型的指针。 也就是
int a[10];
int (*pa)[10] a;a是一个数组在a这个表达式中数组名做左值取整个数组的首地址赋给指针pa。注意a[0]表示数组a的首元素的首地址而a表示数组a的首地址显然这两个地址的数值是相同的但这两个表达式的类型是两种不同的指针类型前者的类型是int ,而后者的类型是int()[10]。
函数类型和函数指针类型
在C语言中函数也是一种类型可以定义指向函数的指针。我们知道指针变量的内存单元存放一个地址值而函数指针存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子:
函数指针
#include stdio.h
void say_hello(const char *str)
{printf(Hello %s\n, str);
}
int main(void)
{void (*f)(const char *) say_hello;f(Guys);return 0;
}分析一下变量f的类型声明void(*f)(const char ),f首先跟号结合在一起因此是一个指针。(*f)外面是一个函数原型的格式参数是const char *,返回值是void,所以f是指向这种函数的指针。而say_hello的参数是const char *返回值是void正好是这种函数因此f可以指向say_hello。注意say_hello是一种函数类型**而函数类型和数组类型类似做右值使用时自动转换成函数指针类型所以可以直接赋给f当然也可以写成void (*f)(const char *) say_hello;**把函数say_hello先取地址再赋给f就不需要自动类型转换了。
可以直接通过函数指针调用函数如上面的f(“Guys”)也可以先用*f取出它所指的函数类型再调用函数即(*f)(“Guys”)。可以这么理解函数调用运算符()要求操作数是函数指针所以f(“Guys”)是最直接的写法而say_hello(“Guys”)或(*f)(“Guys”)则是把函数类型自动转换成函数指针然后做函数调用。
文章来源 https://blog.csdn.net/m0_58367586/article/details/128612310