目录
1、指针
1.指针和地址
定义变量时,系统就会为变量分配内存空间,并为这些空间起个别名就是变量
操作变量有两种方式:
1.直接存取
int a;
a = 10; //直接存数据
printf("a=%d\n", a); //直接取数据
a+=2; // a=a+2; 直接存取数据
int arr[3];
arr[0] = 10; //直接存数据
printf("%d\n", arr[0]); //直接取数据
arr[0] += 3; //直接存取数据
int arr[3] = {1, 2, 3}; //给数组中存数据
2.间接存取(指针参与间接)
int a = 10;
int* p = &a;
*p = 16; //间接存数据
printf("%d\n", *p); //间接取数据
*p = *p+1; //间接存取数据
说明:
当*前面有数据类型,这个*就读作指针,它是一个记号,用于标识用它定义的变量只能存地址
当*前面没有数据类型,这个*读作解引用(间接访问符号);----------------间接访问变量
一个变量的地址就是一个指针
2、指针变量、变量指针
1.变量指针:
变量的指针---变量的地址;可以对变量使用取地址符获取这个变量的地址(&a)
int a;
&a //变量a的地址,也称为变量a的指针(变量指针);
int arr[2]; //定义数组,arr可以看作是一个变量
&arr //变量arr的地址,也是变量arr的指针
2.指针变量:
指针的变量---存储指针的变量;本质是变量,这个变量只能存地址(指针)
定义格式:
数据类型* 变量名;
int* a; //整形指针变量
double* b; //浮点型指针变量
char* ch; //字符型指针变量
//先定义后初始化
int x = 10;
int* px;
px = &x;
//定义并初始化
int y = 20;
int *py = &y;
格式说明:
数据类型:基本数据类型; 需要和初始化时赋值的地址指向的数据的类型一致
* :是一个标记,读作指针;标记这个变量只能存地址
变量名 :合法的标识符
指针变量就是用来存储变量的地址; 指针变量前的基本数据类型和它存的地址指向的变量的数据类型要一致
指针变量的应用(用途):间接访问变量(间接存,间接取)
当指针变量前的*前面有数据类型,这个*是一个标记,用于说明这个变量只能存地址;
读作指针:当指针变量前的*前面没有数据类型,这个*是解引用符,用于间接访问变量(间接存,间接取)
指针变量的应用场景:
1.指针变量间接存取数据
2.指针变量作为形式参数,实际参数给相同数据类型的值(常量&a,变量p)
2.1:交换实参的值案例
2.2:不交换实参的值案例
3.指针变量作为形式参数,可以将函数内的数据传递给函数的调用者
注意:
1.指针变量才能解引用
2.解引用运算符和取地址运算符是互逆操作,他们运算时可以抵消
数组元素的地址赋值给指针变量
int arr[3] = {1, 2, 3};//定义数组
int* p = &arr[0]; //利用数组下标访问数组元素,再使用&
int* p1 = arr; //数组明相当于数组的首地址,可以对指针变量进行赋值
int* p2 = arr+1; //将数组元素的第二个元素赋值给了指针变量
3.指针运算:
前提是这个指针变量指向一个数组,才可以进行指针运算;如果是指向的是基本类型的数据,指针变量运算后没有意义
int a = 10;
int* p = &a;
p = p+1; //此时p+1会指向a变量以外的内存空间,取出来的数是不确定的,大概率是0;
注意:指针变量在运算时,要注意不能越界;越界后数据不安全,甚至有可能会运行报错(段错误)
指针运算,它的结果是跳过这个指针变量基本数据类型的个数
int* p;
p+1; //它和p的内存地址差值为4----4是int的内存空间
p+n; //它和p的内存地址差值为4*n
//p是什么类型的指针,在指针运算时,它就回跳过这个数据类型*n个内存空间
double* p;
double* w = p+2; //w就指向了p所指向的内存空间后8*2个内存单元的位置;8是double所占内存空间的大小
指针变量的运算大多数都是运用再数组元素的访问中
int arr[5];//*(arr+i) 和 arr[i]是等效的
所以数组的遍历函数可以有多种形式
void print1(int arr[], int len)
{
//arr[i]
}
void print2(int arr[], int len)
{
//*(arr+i)
}
void print3(int* arr, int len)
{
//arr[i]
}
void print4(int* arr, int len)
{
//*(arr+i)
}
结论:
1.当指针指向数组时,形式参数可以写成数组形式,也可以写成指针变量的形式
2.当指针指向数组时,arr[i] 和 *(arr+i)是等效
当指针指向数组中元素时,可以进行指针运算:
假设有两个指针变量p, q指向数组中的元素
1.对一个指针变量加一个正整数: p+=2; p++; 但是arr++, arr+=2 写法错误,arr是数组名,不能被赋值
2.对一个指针变量减去一个正整数:p-=2; p--;
3.对一个指针变量自增或自减: p++, ++p; p--; --p;
4.两个指针变量可以相减,他们减的结果是这两个指针跨越元素的个数(而不是内存的差值)
5.两个指针变量不可以相加,编译报错
6.两个指针变量可以比较: p>q, q<q; p==q; p!=q等;两个指针谁在谁的前面或者后面
注意:
解引用与++运算符一起使用是常见的案例辨析:
(自增或自减和解引用运算符同属于一元运算符, 但解引用优先级低于自增或自减)
以下代码前提:p是指针变量,arr是数组名,p默认指向arr的首元素地址
p++; *p; //先使p指针移到下一个元素的地址,然后再取这个地址中的数据
*p++; //先执行*p(解引用),然后p++指向下一个元素; 和*(p++);等效
*(++p); //先执行++p(移动p到下一个元素的地址),然后解引用(取移动后的位置上的数据)
(*p)++; //先执行*p(解引用),然后对解引用的值自增
//如果p指向a数组中的第i个元素,则:
*(p--) //==> a[i--];
*(--p) //==> a[--i];
//例:
int arr[3]={1,2,3};
int* p = arr;
arr[i];
p[i]; //数组元素的下标引用法(下标法)
*(arr+i);
*(p+i) //数组元素通过指针运算引用数据(指针法)
建议:多定义变量,多用括号,代码逻辑就比较清晰
3、数组指针、指针数组
1.数组指针:
本质就是数组的指针;作用是多维数组降低维度;
定义格式:
元素的数据类型 (*变量名)[列数];
short (*p)[4]; //定义了一个数组指针变量p
理解记忆:
1.()优先级最高,括号中的 * 说明了这个变量只能存地址; 优先级高,它就作为主语,后读---指针
2.[]读作数组,优先级低(), 作为定语先读----数组
连起来读:数组的指针,简称数组指针
* * * 数组指针和二维数组是等效的
short arr[3][4]; //定义二维数组
short (*p)[4] = arr; //用二维数组名给指针数组初始化
int arr[2][3][4]; //定义三维数组
int (*p)[3][4]; //定义三维数组的指针变量
p = arr; //用三维数组给数组指针变量赋值
2.指针数组:
指针的数组,本质是一个数组,这个数组中存储的是地址(指针)
定义格式:
数据类型 *变量名[数组的容量];
short *p[4];
理解记忆:
1.*没有带小括号,它的优先级就比中括号优先级要低,作为定语先读它---指针
2.[]读作数组,此时它的优先级高一些,作为主语后读----数组
连起来读:指针的数组,简称指针数组
指针数组应用场景:描述(定义)字符串数组
常见的初始化代码写法:
1.定义的同时赋值
char arr[] = "aaaa";
char* a ="aaaa";
char *arr[3] = {a, "bbbb", "cccc"}; //字符串数组
2.先定义,后赋值
char* arr[4];
char b[] = "ab";
arr[0] = b;
arr[1] = "abc";
arr[2] = "ABCD"
arr[2] = {'a', 'b', 'c'}; //语法错误
//例:
char* arr[4] = {"aaa", "bbbb", "cccc", "dddd"};
char* arr[4];
arr[0] = "aaaa";
char a = 'A';
char* p = &a;
arr[1] = p;
char b[] = "bbbb"; //字符串的第二种定义形式
arr[2] = b;
char* p2 = b;
arr[3] = p2;
char* p2 = "bbbb"; //字符串的第二种定义方式,
C语言中的特殊字符串类型
在C语言中没有字符串类型(string), 字符串的两种定义格式:
char arr[] = "aaaaa"; //用数组存储字符串常量
char* arr = "aaaaa"; //用字符指针记录字符串常量(本质是记录了这个字符串常量的地址)
两种形式的异同点:
1.他们都可以描述字符串
2.两种形式他们存储原理不同;
字符数组存储的是字符串常量的副本;数组名中存的地址和字符串常量的地址不一样
字符指针存储的是字符串常量的地址;字符串常量存储在数据段中
3.字符数组描述的字符串中的字符可以被修改;但字符指针描述的字符串中的字符不能被修改
3.字符数组与字符指针
字符数组和字符指针变量都能指向一个字符串,但他们有区别:
1.字符数组不能先定义,后直接赋值; 字符指针变量可以先定义后赋值
char s[] = "abc" //可以
char* s = "abc"; //可以
char s[];
s = "abc"; //不可以
char* s; s = "abc"; //可以
2.字符数组的数组名中存储的是地址,在指针运算时,数组名不能被赋值; 字符指针变量可以改变
char s[] = "abc";
s = s + 2; //不可以
s++; //不可以;
s+=2; //不可以
char *st = "abc";
st = st+2; //可以
st++;
st+=2; //都可以
3.字符串定义成字符指针,字符串中的字符的可以用下标法取字符,也可以用指针法取字符; 但不能存字符
char *s = "How are do you do!";//该字符串的地址是存放在数据段中,属于常量,不可以改变
printf("%c\n", s[4]); //下标法
printf("%c\n", *(s+4)); //指针法
s[4] = 'D'; //不可以
*(s+4) = 'D'; //不可以
4.字符串定义成字符数组,字符串中的字符可以用下标法或指针法取字符,也可以用下标法或指针法存字符
char s[] = "How are do you do!";//相当于将数据段中的数据复制到栈中一份
printf("%c\n", s[4]); //下标法
printf("%c\n", *(s+4)); //指针法
s[4] = 'D'; //可以
*(s+4) = 'D'; //可以
总结:
1.数组指针作为形式参数,实际参数可以是二维数组名,或者是同类型的数组指针变量名
2.指针数组作为形式参数,实际参数可以是同类型的指针数组名,但不能给二维数组名;可以是二级指针的变量名
3.指针数组应用场景:定义字符串数组