文章目录
1. 指针是什么?
【指针】【重点】
1.1 指针的重要性
表示一些复杂的数据结构
快速的传递数据
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是C语言的灵魂
1.2 指针的定义
地址
内存单元编号
从0开始的非负整数
范围:4G 【0----(4G-1)】
指针
指针就是地址,地址就是指针
地址就是内存单元的编号
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
指针的本质就是一个操作受限的非负整数
- 取地址
我们可以在变量名前加上&
,表示获取变量的地址。
完整代码见001_address.c
指针代表的是地址;
如果输入的是地址,则相当于&变量,得到对应的值;
#include <stdio.h>
int main(){
int m=0;
int n=0;
printf("&m=%p\n",&m);
printf("&n=%p\n",&n);
int* p;
scanf("%p",&p);
scanf("%d",p);
printf("m=%d\n",m);
printf("n=%d\n",n);
}
变量地址通常使用16进制表示,使用%p
或者%P
打印地址。
试一试,多次执行上面的代码编译的可执行文件。
scanf()
实参前面的&
符号
1.3 指针的分类
>1.3.1 基本类型指针【重点】
For example 1:
#include <stdio.h>
int main(void)
{
int * P; //p是变量的名字,int * 表示p变量存放的是int类型变量的地址,即int * 只能存放整型变量的地址
int i = 3;
p = &i; //OK,&是取地址符
/* p = i error, 因为类型不一致,p只能存放int类型的变量的地址,不能存放int类型变量的值 */
/* p = 55; error,原因同上 */
return 0;
}
For example 2:
#include <stdio.h>
int main(void)
{
int * P; /*
p是变量的名字,int * 表示p变量存放的是int类型变量的地址,即int * 只能存放整型变量的地址
int * p;不表示定义了一个名字叫做*p的变量,而应该是p是变量名,p变量的数据类型是int *整型变量指针
相当于这也是一个声明,声明了 int * 是它的数据类型,而后面的p才是它的变量名字
所谓int *;类型--->实际就是存放int变量地址的类型
int *限制了 后面的p只能存放整型变量的地址
*/
int i = 3;
int j;
p = &i; /*
1.p保存了i的地址,因此说p指向i
2.p不是i,i也不是P,更准确的说:修改p的值不影响i的值,修改i的值也不影响p的值
3.如果一个指针变量指向了某个普通变量,则:
*指针变量 就完全等同于 普通变量
例子:
如果p是个指针变量,并且p存放了普通变量i的地址
则p指向了普通变量i
*p 就完全等同于 i
或者说:在所有出现*p的地方都可以替换成i
在所有出现i的地方都可以替换成*p
*p 就是以p的内容为地址的 变量
*/
j = *p; //等价于 j=i;
printf("i=%d,j=%d\n",i,j);
return 0;
}
-------------输出结果为i=3,j=3----------------------------
1.3.2 指针常见错误解析-
For example 1:
#include <stdio.h>
int main(void)
{
int * p;
int i=5;
*P = i; /*error,解析:
因为p开始的时候并没有经过初始化,所以p里面肯定是一个垃圾值,而*P就代表这个垃圾值的地址
且并不知道这个垃圾值是多少,而因为将i赋值给*P,就相当于强行将*p里面垃圾值的地址给
替换成了i的地址。试想,如果有一个软件正在计算机内运行,且占用的内存恰好就为这个垃圾值的地址
那么一旦这样进行替换,这个运行的软件就会崩溃。
*/
printf("%d\n",*P);
return 0;
}
-------这样写的话,整个程序就会出错而也没有输出结果----------
For example 2:
#include <stdio.h>
int main(void)
{
int i= 5;
int * p;
int * q;
p = &i;
/* *q = p; error, *q 和 p的类型不一致,程序会报错 因为*q代表一个变量,而p只代表一个地址 */
/* *q = *p; error, *q 是未知的地址 不能更改未知的地址,相当于上面的例题 */
/* p = q; error, q是垃圾值,q赋给P, p也变成了垃圾值 */
printf("%d\n",*q);
/* q的空间是属于本程序的,所以本程序可以读写q的内容,但是如果q内部是垃圾值,
则本程序不能读写*q的内容,因为*q所代表的内存单元的控制权限并没有分配给本程序。
所以本程序运行到 printf行时就会立即出错。
*/
return 0;
}
2. 指针怎么用?
2.1 定义指针
定义指针与定义变量的方式一样,只不过要在类型和变量名中间加上一个*
星号。
类型* 指针变量;
指针变量只能使用同类型变量的地址赋值。
方式1;定义+初始化;
int n = 10;
int* p;
p = &n;
printf("&n=%p\n",p);
printf("n=%d\n",n);
方式2:可以直接初始化。
int n = 10;
int* p = &n;
printf("&n=%p\n",p);
printf("n=%d\n",n);
变量必须赋值后才能使用,指针也是必须赋值后才能使用。
2.2 解引用
指针的强大之处可以直接操作储存地址里面的数据。这种操作称为解引用。使用时需要在指针前加上*
星号。
注意:这里的
*
与声明指针的含义不一样,与乘号也不一样。
完整代码见002_jieyinyong.c
#include <stdio.h>
int main(){
int n = 10;
int* p = &n;
printf("n=%d\n",n);
printf("*p=%d\n",*p);
n = 100;
printf("n=%d\n",n);
printf("*p=%d\n",*p);
*p = 1000;
printf("n=%d\n",n);
printf("*p=%d\n",*p);
}
在这里对*p
操作就是对n
操作;对n
操作就是对*p
操作。
访问变量两种方式:一是通过变量名直接访问,而是指针解引用访问。
* 号的三种含义:
1.乘法
2.定义指针变量
int * p; //定义了一个名字叫p的变量,int *表示p只能存放int类型变量的地址
3.指针运算符
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则 *p表示 以p的内容为地址的变量
one:
#include <stdio.h>
int main(void)
{
int * p; //等价于int *p; 也等价于int* p;
int i = 5;
char ch = 'A'; //如果要表示单个字符,c语言要求用单引号括起来,如果要表示字符串,用双引号括起来
p = &i; //*p 以p的内容为地址的变量
*p = 99;
printf("i=%d,*p=%d\n",i,*p);
//p = ch; //error
//p = 5; //error
//p = &ch; //error
return 0;
}
----输出结果为i=99,*P=99(因为p=&i,所以当把99赋值给*P的时候,相当于也把99赋值给了i,因为i的地址是不变的)---------
two:检测实参和形参是否是同一个变量
#include <stdio.h>
void f(int x)
{
x = 99; //局部变量只能在定义的函数内使用
}
int main(void)
{
int i = 6; //局部变量
printf("i = &d\n",i);
f(i) //实参和形参一定不是一个变量,跟实参和形参的名字没有关系,不会因为都是i就是一个变量
printf("i = %d\n",i)
return 0;
}
----输出的结果为i = 6, i = 6(证明f(i)的括号里面的i跟上面的x = 99;没有任何关系)-----------
#include <stdio.h>
void swap(int i, int j)
{
int t;
t = i; i = j; j = t;
}
int main(void)
{
int a = 3;
int b = 5;
swap(a,b); //调用swap函数
printf("a = %d,b = %d\n", a, b);
return 0;
}
同样的即便是使用带返回值的函数,也是一样的结果,a=3,b=5
#include <stdio.h>
int swap(int i,int j)
{
int t;
t = i;
i = j;
j = t;
return 0;
}
int main()
{
int a = 3;
int b = 5;
swap(a,b);
printf("a = %d,b = %d \n",a,b);
return 0;
}
-----------------------------------------------
借助指针的地址换换来实现值的互换:
---------------------------------
#include <stdio.h>
void swap(int *i,int *j)
{
int t;
t = *i;
*i = *j;
*j = t;
}
int main()
{
int a = 3;
int b = 5;
swap(&a,&b);
printf("a = %d,b = %d \n",a,b);\
return 0;
}
--------------------这样结果就会成功互换a=5,b=3---------------------------
调用函数的执行过程:
1.当程序在执行调用swap函数的时候,会立马给形参i和j分配内存空间以便用来存储由实参传递过来的a和b的值
2.在swap(int i, int j)接收到实参传递的过来的值以后,将3传递给i,将5传递给j,然后执行{}内的调换运算,
当执行完成之后,i和j的值已经互换了,即i=5和j=3,这时候会立刻释放之前分配给形参i和j的内存空间,然后
程序会继续向下执行到printf("a = %d,b = %d\n", a, b);
3.这个时候因为printf输出的是a和b的值,可是因为a和b的值并没有因为调用swap函数而发生变化,所以依然维持原值输出。
即i = 3, b =5
4.swap函数的内部只是执行了形参内的变量对调,并没有影响到实参。
5.所以要实现上例总的值互换就需要借助指针的地址互换来实现。
---------------------------------------------
- 试一试
int n = 10;
printf("*(&n) = %d\n",*(&n)); //10
3. 指针与函数
3.1 值传递
值传递:地址并未发生改变,所以未发生调用;
试分析下面代码执行结果。
完整代码见00301——value_pass.c
int func(int n){
n = 100;
}
int main(){
int n = 10;
func(n);
printf("n = %d\n",n);
}
10
3.2 指针/地址传递
指针传递:地址发生改变,所以发生调用;
试分析下面代码执行结果。
完整代码见00302——address_pass.c
#include <stdio.h>
int func(int* n){
*n = 100;
}
int main(){
int n = 10;
func(&n);
printf("n = %d\n",n);
}
100
函数内部改变函数外部定义的局部变量必须满足两个条件:
- 指针参数
- 解引用
3.3 如何通过被调函数修改主调函数的的值:
1.实参是普通变量的地址
2.形参必须为指针变量
3.在被调函数中通过
*形参名 = ......
的方式就可以修改主调函数相关变量的值
三种表达:
变量类型 | 实参 | 形参 |
---|---|---|
int n | &n | int* |
int* p | p | int* |
int* p | &p | int** |
4. 实践
- 实现函数
swap()
交换两个变量的值。
方案1:值传递
完整代码见00401——value_swap.c
#include <stdio.h>
void Swap(int m, int n){
int t = m;
m = n;
n = t;
}
int main(){
int n = 10;
int m = 100;
Swap(n,m);
printf("n:%d n:%d", n,m);
cout << "n:" << n << "\tm:" << m << endl;
}
交换不了,n =10; m=100;
方案2:指针传递
#include <stdio.h>
void huhuan_2(int * p, int * q)
{
int * t; //如果要互换p和q的值,则t必须是 int * 不能是 int 否则会出错
t = p;
p = q;
q = t;
return;
/*最终更换的是a和b的地址编号,但实际的值并没有改变
形参的改变不会影响实参*/
}
int main(void)
{
int a = 10;
int b = 100;
huhuan_2(&a,&b); //huhuan(*p,*q); 是错误的,huhuan(a,b); 也是错误的
printf("a=%d,b=%d\n",a,b);
return 0;
}
------------输出的结果还是a=10,b=100依然互换失败--------------------------------
方案3:指针传递
完整代码见00402——address_swap.c
#include <stdio.h>
void Swap(int *m, int *n){
int t = *m; //t表示变量,而非指针;
*m = *n;
*n = t;
}
int main(){
int n = 10;
int m = 100;
Swap(&n,&m);
printf("n:%d n:%d", n,m);
}
可以交换,n =100; m=10;
- 实现函数
divmod()
输入参数a
和b
,同时获取a
与b
的商和余数。
多个返回值:采用指针返回;
完整代码见005——divmod.c
#include <stdio.h>
int Divide(int n , int m, int* mod){
int div = n/m;
*mod = n%m;
return div;
}
int main(){
int m = 101;
int n = 10;
int mod;
int div = Divide(m,n,&mod);
cout << "div:" << div << "\tmod:" << mod << endl;
}
div:10 mod: 1;
指针在函数中有这两种应用,一种是即作为输入又作为输出;另一种只作为输出。
5. 练习
一个函数最多一个返回值
- 实现函数
triangle()
输入参数a
、b
和c
,返回能否构成三角形,如果能够构成三角形从参数获得周长和面积。
#include<iostream>
#include<cmath>
using namespace std;
bool isTriangle(int a, int b, int c) {
// 判断三条边长度是否能构成三角形
if (a + b > c && a + c > b && b + c > a)
return true;
else
return false;
}
double getArea(int a, int b, int c) {
if (isTriangle(a, b, c)) {
double p = (a + b + c) / 2.0; // 半周长
return sqrt(p * (p - a) * (p - b) * (p - c)); // 海伦公式
}
else
return -1; // 表示不能构成三角形
}
int main() {
int a = 3, b = 4, c = 5;
if (isTriangle(a, b, c)) {
cout << "Perimeter: " << a + b + c << endl;
cout << "Area: " << getArea(a, b, c) << endl;
} else {
cout << "These sides cannot form a triangle." << endl;
}
return 0;
}
- 实现函数
circle()
输入参数r
,从参数获得周长和面积。
#include<iostream>
#include<cmath>
using namespace std;
double getPerimeter(double r) {
return 2 * M_PI * r;
}
double getArea(double r) {
return M_PI * r * r;
}
int main() {
double r = 5;
cout << "Perimeter: " << getPerimeter(r) << endl;
cout << "Area: " << getArea(r) << endl;
return 0;
}