内存中的最小单元是字节,一个字节对应一个编号,这里的编号就是对应字节的地址。换句话说,地址就是内存单元的编号。
指针与指针变量是两个不同的概念,指针是某个普通变量的地址,所以可以理解为,指针就是地址,地址就是指针。指针变量是一种变量,它的作用是存储其它变量的地址。
#include
int main()
{
int * p; // int *是指针类型,p是对应的变量,定义的指针变量p只能用来存储int类型变量的地址
int i = 3, j;
p = &i; // 指针变量只能用来存储对应类型变量的地址,所以这里需要对变量i进行取地址操作,即&i
/*
指针变量p保存了变量i的地址,这样的话,我们称:p指向i。通俗地说,通过p可以找到i
虽然p指向i,但变量p和变量i不是同一个变量
更准确地说,修改其中任何一个变量p值不会影响变量i,反之同理
*/
printf("%d %d\n", *p, i);
/*
如果一个指针变量指向某个普通变量,则在指针变量前加上*后就完全等同于指向的普通变量
换句话说,可以通过指针变量前加*来找到那个指向的普通变量
以本程序为例,这里的*p就是以p的内容为地址的变量
*/
j = *p; // *p就是i,所以相当于将i的值赋给了j
printf("i = %d, j = %d\n", i, j); // 输出结果为i = 3, j = 3
return 0;
}
指针是C语言的灵魂
错误1:指针变量无明确指向
#include
int main()
{
int * p;
int i = 5;
*p = i; // 出错行
printf("%d\n", *p);
return 0;
}
错误原因:由于p
未初始化,所以p
的内容是垃圾值。*p
是以p
的内容为地址的变量,即以一个垃圾值为地址的变量,该变量是未知的,所以当把i
的值赋值给p
指向的未知变量时,有可能会篡改内存中其它变量的值,而这样的操作是不允许的。
错误2:赋值时变量类型不一致
#include
int main()
{
int * p;
int * q;
int i = 5;
p = &i;
*q = p; // 出错行
printf("%d\n", *q);
return 0;
}
错误原因:由于p是指针类型变量,而*q是int类型变量,所以不能相互复制。
f
函数中的变量i
与main
函数中的变量i
是不是同一个变量?f
函数中的变量i
与main
函数中的变量i
都属于局部变量,只在自己对应的函数中起作用,所以,f
函数中的变量i
与main
函数中的变量i
不是同一个变量。#include
void f(int i)
{
i = 99;
}
int main()
{
int i = 66
printf("%d\n", i);
f(i);
printf("%d\n", i);
return 0;
}
f
函数修改main
函数中变量i
的值?f
函数中的变量i
与main
函数中的变量i
不是同一个变量,所以把实参i
传递给形参i
只会改变f
函数中变量i
的值,当f
函数执行完毕后,分配给形参i
的空间会被释放,故而无法改变main
函数中变量i的值。换句话说,f
函数中的变量i
与main
函数中的变量i
本质上没有任何关系,所以不管怎么修改f
函数中变量i
的值都不会影响main
函数中变量i
的值。那要如何才能通过其它函数来修改主调函数中的值?
此时,指针就派上用场了,如下述程序:
#include
void f(int * p) // 通过接收地址来确定要修改的变量
{
*p = 99; // *p就是以p变量的内容为地址的变量,也就是要通过该函数修改的变量
}
int main()
{
int i = 66
printf("%d\n", i); // 输出结果为66
f(&i); // 由于函数f的形参是指针变量,故需将变量i的地址发送过去
printf("%d\n", i); // 输出结果为99
return 0;
}
上述程序可以实现在被调函数中修改主调函数中变量的值是因为通过向被调函数传递了需要修改的变量的地址,从而确定并指向了需要修改的变量,但如果不传入地址,就会导致主调函数中的变量无法与被调函数产生关联,从而无法实现目的。
swap
函数,用该函数互换main
函数中的两个变量的值常见错误:只传入数值,不传入地址
void swap(int a, int b)
{
int t;
t = a;
a = b;
b = t;
}
int main()
{
int a = 3, b = 5;
swap(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
出现上述错误的原因是,main
函数中的变量a
和b
与swap
函数中的形参a
和b
无关,导致的结果是,主函数将3
和5
发送给形参a
和b
后,swap
函数只是对3
和5
进行了操作,而未能对main
函数中的变量a
和b
进行操作,所以无法互换main
函数中的变量a
和b
的值。
正确实现方法:传入地址,定位需要互换的变量
void swap(int * p, int * q)
{
int t;
t = *p;
*p = *q;
*q = t;
}
int main()
{
int a = 3, b = 5;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
#include
void swap(int * p, int * q)
{
int * t;
t = p;
p = q;
q = t;
}
int main()
{
int a = 3, b = 5;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
答案是不行的,上述程序将变量a
和b
的地址发送给了指针变量p
和q
,此时,变量p
和q
中储存的是变量a
和b
的地址,然而,swap
函数中的操作是互换变量p
和q
的内容,也就是说,当swap
函数执行完毕后,变量p
中储存的是变量b
的地址变量q
中储存的是变量a
的地址,言下之意,只是将变量p
和q
的内容互换了而已,并没有对main
函数中的变量a
和b
进行操作,所以无法实现互换功能,此外,几乎所有的编程语言都无法通过互换两个变量的地址实现互换变量中的值。
#include
int main()
{
int a[5];
int b[5];
a = b; // 错误,因为a和b都是常量,所以无法进行赋值操作
a = &a[2]; // 错误,因为a是常量,无法对一个常量进行赋值操作
return 0;
}
#include
int main()
{
int a[5];
printf("%#X", &a[0]);
printf("%#X", a); // 与上一行输出结果相同,因为一维数组名就是数一维组中第一个元素的地址
return 0;
}
a[i]
表示第i+1
个元素*(a+i)
表示第i+1
个元素#include
int main()
{
int a[5];
int i;
for (i = 0; i < 5; i++) // 向一维数组中读入元素
scanf("%d", &a[i]); // 通过下标引用数组中的元素
for (i = 0; i < 5; i++) // 输出一维数组的内容
printf("%d ", *(a + i)); // 通过指针引用数组中的元素
return 0;
}
#include
int main()
{
int a[5];
int * p = &a[1];
int * q = &a[4];
printf("%d\n", q - p); // 输出结果为3,证明相隔3个元素
return 0;
}
使用函数对一维数组进行操作,首先要将数组名传递给函数,因为一维数组名是函数第一个元素的地址,传递数组名就相当于传递起始位置,其次,普通数组不同于字符数组,它们没有结束的标志,所以还需要向函数传递数组长度以确定数组何时结束。故想要在另外一个函数中对一维数组进行操作需要向该函数传入两个参数,数组名和数组长度。
定义函数时的形参有两种写法,第一种是(int a[], int length)
,第二种是(int * a, int length)
。可以写第二种的原因是一位数组名本身就是指针常量,所以可以直接用指针变量来接收。
#include
// 自定义的print函数,其功能是将一维数组输出
void print(int a[], int length)
{
int i;
for (i = 0; i < length; i++)
printf("%d ", a[i]); // 也可以写成printf("%d ", *(a+i));
printf("\n");
}
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int b[6] = { -1, -2, -3, -4, -5, -6 };
int c[100] = { 23, 88, 99, 44 };
print(a, 5);
print(b, 6);
print(c, 100);
return 0;
}
#include
int main()
{
char c = 'A';
int i = 99;
double x = 66.66;
char * p = &ch;
int * q = &i;
double r = &x;
printf("%d %d %d\n", sizeof(c), sizeof(i), sizeof(x)); // 输出结果为1 4 8
printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r)); // 输出结果均为4
return 0;
}
上述程序证明,尽管普通类型变量所占的空间大小不一致,但它们对应的指针变量都占四个字节。
malloc这个词是由memory(内存)与allocate(分配)这两个单词合成的,顾名思义,malloc函数就是用来分配内存的函数。
#include
#include
int main()
{
int i; // 静态分配了4个字节的存储空间
int* p = (int*)malloc(4);
/*
1.要使用malloc函数,需添加malloc.h头文件
2.malloc函数只有一个形参,其类型为整型
3.实参中的4表示请求操作系统为本程序分配4个字节的动态存储空间
4.malloc的返回值是分配给该系程序的第一个字节的地址
5.由于第一个字节的地址不能确定具体的变量类型,所以需要强制类型转换
6.第7行代码一共分配了8个字节的存储空间,其中变量p占4个字节,p指向的空间也占是4个字节
7.变量p所占的内存是静态分配的,p所指向的内存是动态分配的
*/
free(p);
/*
free(p)表示释放p所指向的内存
free函数只能用来释放动态内存,不能用来释放静态内存,静态内存只能由操作系统自动释放
free(p)只是释放了p对应的内存空间,但p的内容依旧存在
*/
return 0;
}
#include
#include
int main()
{
int a[10]; // 静态构造一维数组
int length;
int* pArray;
int i;
scanf("%d", &length);
pArray = (int*)malloc(sizeof(int) * length);
/*
动态构造一维数组
该动态数组数组名为pArray,数组长度为length,数组中每个元素都是int类型
*/
// 对该动态一维数组手动赋值
for (i = 0; i < length; i++)
scanf("%d", &pArray[i]);
// 输出该动态一维数组的内容
for (i = 0; i < length; i++)
printf("%d ", *(pArray + i));
printf("\n");
free(pArray); // 释放该动态数组
return 0;
}
静态内存是由操作系统自动分配,自动释放的。静态内存是在栈中分配的;动态内存是由程序员手动分配,手动释放,但如果只分配不释放就会导致内存泄露,也就是内存越用越少。动态内存是在堆中分配的。
#include
int main()
{
int i = 10;
int * p = &i;
int ** q = &p;
int *** r = &q;
r = &p; // 错误,因为r是int *** 类型,它只能用来存储int ** 类型变量的地址
printf("i = %d\n", ***r);
return 0;
}
表解上述程序:
变量名 | 变量地址 | 变量内容 |
---|---|---|
i | 1000H | 10 |
p | 2000H | 1000H |
q | 3000H | 2000H |
r | 4000H | 3000h |
变量名 | 对应变量 |
---|---|
*r | q |
**r | p |
***r | i |
#include
void g(int ** q) // 由于p的类型是int *,所以q的类型必须是int **,因为q要用来存放p的地址
{
}
void f()
{
int i;
int * p;
p = &i;
g(&p); // 要通过g函数修改p的内容,则必须发送p变量的地址
}
int main()
{
f();
return 0;
}
由于静态内存是在栈中分配的,而函数执行完毕后,栈中的静态内存就会全部出栈,而动态内存是在堆中分配的,当函数执行完毕后,堆中分配的内存并不会像栈中分配的内存一样直接被释放掉,所以
静态内存是不能跨函数使用的,而动态内存是可以的。
#include
void f(int** q)
{
int i = 5;
*q = &i; // 由于q储存了p的地址,所以*q就是p,这行代码实质是将i的地址赋给了p
}
int main()
{
int* p;
f(&p);
printf("i = %d\n", *p); // 由于p储存了i的地址,所以*p就是i
return 0;
}
上述程序没有语法错误,但是有逻辑上的错误,这是因为,当f
函数执行完毕后,f
函数中所有的静态变量的内存都会被释放掉,所以当执行到printf("i = %d\n", *p);
时,p
所指向的变量空间的访问权限已经返还给了操作系统,这样就会导致*p
访问了不属于该程序的空间。这个程序说明了在一个函数内部定义的静态变量在该函数中执行完毕后就不再可以垮函数使用。
#include
void f(int** q)
{
*q = (int*)malloc(sizeof(int));
**q = 5;
}
int main()
{
int* p;
f(&p);
printf("%d\n", *p);
return 0;
}
上述程序是完全没有语法错误的,因为当f
函数执行完毕后,其中分配的动态内存不会自动释放,所以在main
函数中依然可以使用这段内存。这个程序体现出,在一个函数中分配的动态存储空间在该函数执行完之后,仍然可以在另外一个函数中使用。
fun
函数使main
函数中的指针变量p
指向一个合法的整型单元?#include
void fun(int * p)
{
int s;
p = &s;
}
int main()
{
int * p;
fun(p);
return 0;
}
#include
void fun(int ** p)
{
int s;
*p = &s;
}
int main()
{
int * p;
fun(&p);
return 0;
}
#include
#include
void fun(int * p)
{
p = (int *)malloc(sizeof(int));
}
int main()
{
int * p;
fun(p);
return 0;
}
#include
#include
void fun(int ** p)
{
*p = (int *)malloc(4);
}
int main()
{
int * p;
fun(&p);
return 0;
}