9.1指针基本概念入门
内存中字节的编号称为地址,地址就是某块内存所在的位置,指针指向的是就是地址。
#include <stdio.h>
int main() {
unsigned int a = 100;
char str[] = "c.biancheng.net";
printf("%#X, %#x\n", &a, str);//是否输出Ox
return 0;
}
字符串名本身就代表了字符串的首地址。
C语言的一切都是地址,所以指针用的如何决定了C语言的功底。C语言用变量来存储数据、用函数定义一段可以重复使用的代码,最终所有的数据放到内存中才能够使用。
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存存储的是数据还是代码。当程序被加载到内存后操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,拥有读取和写入权限的就是数据,因为代码是用来执行的,而数据是无法执行的。
CPU访问内存时需要的是地址,而不是变量名和函数名,变量名和函数名只是地址的助记符,当源文件被编译和链接成可执行程序后,他们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
在编码的过程中,我们初步认为变量名代表的数据本身,取得是数据的地址&a,函数名、字符串名、数组名表示的是数据块的首地址或者代码块。
9.2指针变量的定义与使用
指针变量:指针变量是变量,但是该变量不同于一般的变量它是指针变量,这就代表这个变量存储的是数据的指针,数据在内存中的地址就是指针。
P是指向变量C的指针
P(11A)—C(‘L’)(11A)
定义指针变量(加*):
int *p=&a;
给指针变量赋值(不加*):
p=&a
p=&b
p=&c
表明这个变量是指针变量,
定义指针变量的时候必须用 *
给指针变量赋值的时候就不需要再加了
因为已经知道它是一个指针变量
区分整型 整型指针
字符型、字符型指针
浮点型、浮点型指针
通过指针变量取得数据:
指针变量存储的是数据的地址,通过指针变量可以获得改地址上的数据。
指针是间接获取数据,变量名是直接获取数据。
通过指针修改内存上的数据、通过指针获取内存上的数据:
#include<stdio.h>
int main() {
int a = 15, b = 99, c = 222;
int *p = &a;
*p = b;//通过指针变量修改内存上的数据
c = *p;//通过指针变量获取内存上的数据
printf("%d\n%d\n%d\n%d\n",a,b,c,*p);
return 0;
}
*在定义指针变量和使用指针变量时的应用:
在定义指针变量的时候表示这是一个指针变量而不是普通的变量
在使用指针变量的时候,由于p是指针变量,用就可以取指针指向的数据。
int *p=&a;
*p=100;
*p=b=299;
int *p;
p=&a;
*p=200;
注:指针变量也是变量,只不过作为一种常用的变量拉出来单独讲解而已。
9.3指针变量的应用
指针变量指向的是地址、地址也是数,所以可以进行加减、比较这些运算。
9.4数组指针
什么是数组指针:数组指针也是指针,指针指向的是数组。
数据是一系列相同数据的集合,每一份数据叫做一个数组元素。数组中的每个元素在内存中连续排列,整个数组占据的是一块内存,数组里面的元素数据类型相同,因此地址的间隔也相同,这样就可以利用地址的加减法访问数组内不同的元素。
定义数组的时候,需要给出数组名,数组名代表指向数组第0个元素的指针。在C语言中,我们将第0个元素的地址称为数组的首地址。
用指针遍历数组;
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55};
int len;
len = sizeof(arr) / sizeof(int);
for (int i = 0; i < len; i++)
{
printf("%d\n",*(arr+i));
}
return 0;
}
定义一个数组指针:
int arr[]={11,22,33,44,55}
int *p=arr;
因为arr本身就是一个指向数组第0个元素首地址的指针类似于&arr[0]的作用。
注:数组指针指向的是数组中一个具体的元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。
使用数组指针遍历数组元素:
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55};
int *p = arr;
int len;
len = sizeof(arr) / sizeof(int);
for (int i = 0; i < len; i++)
{
printf("%d\n",*(p+i));
}
return 0;
}
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55};
int *p = arr;
int len;
len = sizeof(p) / sizeof(int);
for (int i = 0; i < len; i++)
{
printf("%d\n",*(p+i));
}
return 0;
}
对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的,如果一个指针变量p指向了数组的某个元素,p+1访问下一个元素、p-1访问上一个元素,进而完成对整个数组的访问。
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55};
int len = sizeof(arr) / sizeof(int);
for (int i = 0; i < len; i++)
{
int *p = &arr[i];
printf("%x\n", p);
}
return 0;
}
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55};
int len = sizeof(arr) / sizeof(int);
int *p = &arr[3];
printf("%d\n%d\n%d\n%d\n%d\n",*(p-3),*(p-2),*(p-1),*(p),*(p+1));
return 0;
}
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55 };
int len = sizeof(arr) / sizeof(int);
int *p = &arr[0];
for (int i = 0; i < len; i++)
{
printf("%d\n", p[i]);
}
return 0;
#include<stdio.h>
int main() {
int arr[] = {
11,22,33,44,55 };
int len = sizeof(arr) / sizeof(int);
int *p = &arr[0];
for (int i = 0; i < len; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
9.5字符串指针
C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中。
#include<stdio.h>
int main() {
char str[] = "hellosjl";
int len = strlen(str);
for (int i = 0; i < len; i++)
{
printf("%c",str[i]);
}
printf("\n%s",str);
return 0;
}
注:字符数组也是数组呀,关于指针和数组的规则同样也适用于字符数组。
使用指针的方式输出字符串:
#include<stdio.h>
#include<string.h>
int main() {
char str[] = "hellosjl";
int len = strlen(str);
int i;
for (int i = 0; i < len; i++)
{
char *p = &str[i];
printf("%c",*p);
}
return 0;
}
使用一个指针指向字符串:
char *str=“hellosjl”;
或者
char *str;
str=“hellosjl”;
注:字符串的所有字符在内存中也是连续排列的。
注:字符数组存储在全局数据区或者栈区,有读取和写入权限;
字符串存储在常量区,只有读取权限;
注:常用字符串数组(功能更全)
9.6C语言数组的访问形式
printf输出字符串的时候,要求给出一个起始地址,并从这个地址开始输出,直到遇见字符串结束标志符。
字符与整数运算的时候,现将字符转换成对应的ASCII码整数值。
65-90 A-Z
97-122 a-z
字符串数组的修改、读取、
#include<stdio.h>
int main() {
char str[20] = {
0 };//将所有元素初始化为\0 这样在循环结束时就无须添加字符串结束标志
for (int i = 0; i < 10; i++)
{
*(str + i) = 97 + i;
}
printf("%s\n",str);
printf("%c\n",str[2]);
printf("%s\n",str+2);
printf("%c\n",(str+2)[2]);
return 0;
}
9.7指针变量作为函数参数
用指针变量作为函数参数可以将函数外部的地址传递到函数内部,使得函数内部可以操作函数外部的数据,这些数据不会随着函数的结束而销毁。
函数的参数可以是整数、小数、字符等数据以及指向他们的指针。
区分函数内部与外部:
#include<stdio.h>
void swap(int *a, int *b) {
int temp;
temp = *a;
*a = *b;
*b=temp;
}
int main() {
int a = 100, b = 200;
swap(&a, &b);
printf("%d\n%d\n",a,b);
return 0;
}
用数组作为函数参数:
数组保存的是一系列相同数据的集合,无法通过参数将他们一次性传递到函数内部。如果希望在函数内部操作数组,就必须传递数组指针,这样才能操操作整个数组。
#include<stdio.h>
int max(int *arr, int len)
{
int mv=arr[0];
for (int i = 0; i < len; i++)
{
if (mv<arr[i])
{
mv = arr[i];
}
}
return mv;
}
int main() {
int arr[3];
int len = sizeof(arr) / sizeof(int);
for (int i = 0; i < len; i++)
{
scanf_s("%d",arr+i);
}
int v = max(arr, len);
printf("%d\n",v);
}
C语言为什么不允许直接传递数组的所有元素,而必须传递数组指针?
参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝,所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。
9.8指针函数
函数就是一种作用规则,函数应该有返回值,返回值是指针的函数就是指针函数。
C语言允许函数的范围值是一个指针,C语言允许函数的返回值是一个地址。
指针函数:函数的返回值是一个指针。
#include<stdio.h>
#include<string.h>
char *strlong(char *str1, char *str2)
{
if (strlen(str1)>strlen(str2))
{
return str1;
}
else
{
return str2;
}
}
int main() {
char str1[30];
char str2[20];
char *str;
gets(str1);
gets(str2);
str = strlong(str1, str2);
printf("%s\n",str);
return 0;
}
注:用指针作为函数返回值时,函数运行后会在销毁它在内部定义的所有局部数据,包括局部变量、局部数组、形式参数,因此函数返回的指针不要指向上述的局部数据,如果出现这样的操作C语言中这些数据可能失效,在后续程序运行过程中引发运行错误。
指针作为函数返回值之后,函数运行结束后会销毁所有的局部数据,这里所说的销毁不是将局部数据所占用的内存全部抹掉,而是程序放弃他的使用权限,后面的代码可以随意使用这块局部数据占用的内存。
#include<stdio.h>
#include<string.h>
int *func() {
int n = 100;
return &n;
}
int main() {
int *p = func();
int n ;
printf("hahah\n");
n = *p;
printf("%d\n",n);
return 0;
}
9.9指向指针的指针
直接访问
间接访问:一次、二次、三次、、、n次
一次:指针指向基本数据类型
二次:指针指向指针类型的数据
指针变量具有变量的属性,变量需要占用存储空间,可以使用取地址符&取地址。
注:C语言不限制指针的层级数量,每增加一级指针,就多一个号。
注:几级指针就有几个号
#include<stdio.h>
int main() {
int a = 100;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
printf("%d\n%d\n%d\n%d\n",a,*p1,**p2,***p3);
printf("%#X\n%#X\n%#X\n%#X\n",&a,p1,*p2,**p3);
printf("%#X\n%#X\n%#X\n",&p1,p2,*p3);
printf("%#X\n%#X\n",&p2,p3);
printf("%#X\n",&p3);
return 0;
9.10空指针NULL以及void指针
指针变量很强大,他可以指向计算机中的任何一块内存,无论该内存是否分配、是否有使用权限,只要把地址给了指针,他就可以指向改地址。因为它如此强大所以导致C语言指针变量指向内存正确性缺乏保护机制,这取决于程序员的能力。
注:指针变量是变量,变量在定义之前必须初始化。变量如果没有初始化就不要使用,否则就出现无法定位的问题。
因为未初始化的变量的值是不确定的,指向内存的位置和权限也是未知的。不能随意的读写。
NULL与null的意义完成不同,NULL作为大写字符有着特殊意义,在C语言中代表空指针。
判断空指针的函数:
void func(char *p) {
if (p==NULL)
{
printf("null\n");
}
else
{
printf("%s\n",p);
}
}
空指针在stdio.h的定义:
#define NULL((void*)0)
强制类型转换
空指针指向地址为0的内存。
C语言标准库以及标准头文件要慢慢熟悉!
注:在进程的虚拟地址空间中,最低地址处有一段内存区域称为保留区,这个去太原不存储有效数据、不能被用户程序访问,因此将NULL指向这块区域很容易检测到违规指针。
注:在大多数操作系统中,极小的地址通常不保存数据,也不允许被程序访问,作为保留区,因此NULL可以指向这段地址区间的任何一个地址。
注:大部分标准库约定俗成将NULL指向0,但是NULL和0不能等同。
注:空指针用NULL而不是用0;
注:NUL表示字符串结束标志\0 也就是空字符
void指针:
void用在函数定义中可以表示函数没返回值或者没有形式参数
没有返回值:
void func(char *p) {
if (p==NULL)
{
printf("null\n");
}
else
{
printf("%s\n",p);
}
}
有返回值:
#include<stdio.h>
#include<string.h>
int *func() {
int n = 100;
return &n;
}
int main() {
int *p = func();
int n ;
printf("hahah\n");
n = *p;
printf("%d\n",n);
return 0;
}
void指针在#define NULL((void*)0)表示NULL指针指向的数据类型是未知的。
void*是一个真正的指向某种类型数据的指针,但是数据类型是未知的。在后续的使用过程中需要进行强制类型转换。
#include<stdio.h>
int main() {
char *str=NULL;
str = (char *)malloc(sizeof(char) * 20);
gets(str);
printf("%s\n",str);
return 0;
}
注:编程的时候要按照一定思路,最好针对每个程序画一个流程图,反正可以加强visio
9.11数组和指针不能绝对等价,数组是另外一种类型
变量名用来指代一份数据,数组名用来指代一组数据。
指向数组的指针变量只是一个变量,而不是一组数据,指针可以指向数组首地址也可以是其他起始地址。
#include<stdio.h>
int main() {
int a[10] = {
11,22,33,44,55 };
int *p = a;
int len1 = sizeof(a) / sizeof(int);
int len2 = sizeof(p) / sizeof(int);
printf("%d\n%d\n",len1,len2);
return 0;
}
注:函数是一种映射规则,有自变量和因变量。查找映射是运算符映射,已经规定好,自变量和因变量是固定的,不受运算作用,但是受到运算符映射的作用。
9.12数组转换成指针
指针是指针
数组是数组
指针和数组有时候可以等同理解,但是两者不是绝对等价。
数组名本身就代表了数组首元素的地址。
数组名代表数组本身还是指针的辨析:
C语言标准规定,当数组名作为数组定义的标识符(定义或声明数组)、sizeof的操作数时,他才表示整个数组本身,在其他的表达式中,数组名会被转换成指向第0个元素地址的指针。
C语言标准规定对数组下标的引用,总是可以写成一个指向数组的起始地址的指针加上偏移量。
数组作为函数参数:
C语言标准规定,作为类型的数组的形参应该调整为类型的指针,数组名代表数组的首地址,在函数形参定义的情况下,编译器必须把数组形式改写成指向数组第0个元素的指针形式,编译器只向函数传递数组的地址,而不是整个数组的拷贝。
数组和指针的可交换性总结:
用a[i]这样形式对数组进行访问总是会被编译器改 写成
*(a+i)这样指针的形式(数组有很多数据我不一定会全部使用,只要我用的时候知道在哪里就行)
指针不能被改写成数组;
特定情况下数组作为函数形参的这种情况,一个数组可以看做是一个指针,作为函数形参的数组始终会被编译器修改成指向数组第一个元素的指针。
如果想要向函数传递数组,可以把函数参数定义为数组形式,也可以定义为指针,但是在函数内部数组作为形参或者定义为指针都要视为指针变量。
9.13指针数组
指针数组:指针数组是数组,只不过数组里面的元素是指针,在指针涉及的概念中,后面才是真正的原型,前面是定语,用来解释说明。
前置知识:C语言运算符优先级
后缀运算符:[] () . -> ++ –
一元运算符:! * &
指针数组:int *a[6]
#include<stdio.h>
int main() {
int a = 11, b = 22, c = 33;
int *arr[3] = {
&a,&b,&c };
int **arr2 = arr;
printf("%d\n%d\n%d\n",*arr[0],*arr[1],*arr[2]);
printf("%d\n%d\n%d\n",**(arr2+0),**(arr2+1),**(arr2+2));
return 0;
}
指针数组和字符串的结合使用:
#include<stdio.h>
int main() {
char *s = "haojizhi";
printf("%c",*(s+1));
printf("%s", s);
return 0;
}
指针数组、字符串、访问字符串、访问字符串的元素、指针、指针变量、访问地址、访问元素
#include<stdio.h>
int main() {
char *str1 = "hahaha";
char *str2= "nihao";
char *str3 = "haojizhi";
char *str[3] = {
str1,str2,str3 };
printf("%c\n%c\n%c\n",*(str1+1),*(str2+1),*(str3+1));
printf("%s\n%s\n%s\n",str1,str2,str3);
printf("%s\n%s\n%s\n",str[0],str[1],str[2]);
return 0;
}
#include<stdio.h>
int main() {
int *p = 100;
char *str1 = "hahaha";
char *str2= "nihao";
char *str3 = "haojizhi";
char *str[3] = {
str1,str2,str3 };
printf("%#x\n",p);
printf("%c\n%c\n%c\n",*(str1+1),*(str2+1),*(str3+1));
printf("%s\n%s\n%s\n",str1,str2,str3);
printf("%s\n%s\n%s\n",str[0],str[1],str[2]);
return 0;
}
9.14利用程序精通指针数组和二级指针
指针数组:数组的每个元素都是指针(本质是数组)
二级指针:指向指针的指针(本质是指针)
#include<stdio.h>
int main() {
char *str1 = "hahaha";
char *str2 = "nihao";
char *str3 = "haojizhi";
char *str[3] = {
str1,str2,str3};
printf("%s\n",str1);//hahaha
printf("%c\n",*(str1+1));//a
printf("%c\n",*str[2]+2);//h
printf("%c\n",*(*(str+2)+1));//a
return 0;
}
注:指针数组会被转换成二级指针,指向字符的指针、字符。
#include <stdio.h>
int main(){
char *lines[5] = {
"COSC1283/1284",
"Programming",
"Techniques",
"is",
"great fun"
};
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
return 0;
}
9.15二维数组指针
二维数组:二维数组在概念是二维的,有行和列,然而在内存中数组元素都是连续排列的,整个二维数组占用的一块连续的内存。
C语言允许把一个二维数组分解成多个一维数组处理。
指针、数组、二维数组,编程中蕴含的思想。
基本数据类型和非基本数据类型:
数组名出现在表达式中会被转换为指向数组第0个元素的指针。
用指针遍历二维数组:
#include<stdio.h>
int main() {
int a[3][4] = {
{
1,2,3,4},{
5,6,7,8},{
9,10,11,12} };
int(*p)[4] = a;
int i, j;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d\n",*(*(p+i)+j));
}
}
return 0;
}
二维数组指针:数组是二维数组,为了实现对数组的访问,采用指针访问二维数组,因此二维数组指针是指针,指针指向的数组是特殊的数组,这个特殊的数组数是二维数组。
注:指针本身占用内存的字节数量取决于指针指向的类型。
9.16函数指针
函数指针本身是指针,只不过这个这个指针指向的是函数本身。
函数原型的成员列表:
参数类型 参数名
函数指针
函数指针的调用
#include<stdio.h>
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int x, y,mv;
int (*p)(int a, int b)=max;
printf("input two number\n");
scanf_s("%d%d",&x,&y);
mv = (*p)(x,y);
printf("%d",mv);
return 0;
}
9.17彻底理解指针
运算符的优先级:
定义中被括号()括起来的部分
后缀操作符:括号()表示这是一个函数,[]表示这是一个数组
因为存在函数指针这个概念
前缀操作符:指向*的指针
一层层剥开你的心
9.18main函数接收用户输入的数据
int main(int argc,char *argv[])
上述原型可以实现在程序启动时给程序传递数据用户输入的多份数据在程序中表现为字符串。
argc :argument count 传递字符串的数目
argv:argument vector 指针数组,指针指向字符串(数据)
D:\VScode\C\argcandargv\Debug>.\argcandargv.exe
receive 1 data:
.\argcandargv.exe
D:\VScode\C\argcandargv\Debug>.\argcandargv.exe hahaha
the program receive 2 data:
.\argcandargv.exe
hahaha
D:\VScode\C\argcandargv\Debug>.\argcandargv.exe hahaha nihao
the program receive 3 data:
.\argcandargv.exe
hahaha
nihao
在linux中,每个shell命令都需要一个程序来解释,如果这个程序由C编写,那么main函数就可以接收这个命令以及他后面的附加参数
注:应用于场强测量系统的回放
左对齐与右对齐
输出宽度
格式化输入与输出
判断一个数是否为质数:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int isPrime(n);
int main(int argc, char *argv[]) {
if (argc<=1)
{
printf("error\n");
}
else
{
for (int i = 0; i < argc; i++)
{
int n = atoi(argv[i]);
int result = isPrime(n);
if (result<0)
{
printf("error input\n",n);
}
else if(result)
{
printf("is prime\n",n);
}
else
{
printf("is not prime\n",n);
}
}
}
}
//判断是否是素数
int isPrime(int n) {
int i, j;
if (n <= 1) {
//参数错误
return -1;
}
else if (n == 2) {
//2是特例,单独处理
return 1;
}
else if (n % 2 == 0) {
//偶数不是素数
return 0;
}
else {
//判断一个奇数是否是素数
j = (int)sqrt(n);
for (i = 3; i <= j; i += 2) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
}
9.19C语言指针小结
指针就是内存的地址,C语言最大的优势就是指针可以实现对内存的访问,C语言允许用一个变量来存放指针,这种变量称为指针变量。
指针变量可以存放基本数据类型、数组、函数、其他指针变量的地址,所以指针的功能很强大,要熟练应用指针。
程序运行最终需要的是地址,因为程序运行的数据在内存放着,变量名、函数名、字符串名、数组名本质上是地址的助记符,帮助编程者理解,以避免直接对内存操作。
在编码的过程中,我们认为变量名表示的就是数据本身;
函数名、字符串名、数组名是数据的首地址,帮助我们找到一串数据的位置,我们使用数据的时候只需要知道他们在哪里即可,不用记下全部。
常见指针变量的定义:
指针变量的加减与指针指向的数据类型有关
指针变量是变量,一般的变量是赋值,指针变量是特殊的变量,特殊在于给指针变量赋值的时候需要将数据的地址赋给指针变量
int *p;
p=&1000;
指针变量也是变量,变量使用之前必须初始化,否则会导致程序崩溃,对于指针变量的初始化,暂时没有指向的指针需要指向NULL,这就是空指针。
两个指针变量的加减取决于数据类型;
数组是有数据类型的,数组名的本意是一组相同数据类型的集合。在表达式中数组名会转换成一个指针数组首元素地址的指针。
使用sizeof时,数组名表示整个数组。