0.问题引入
int a = 5;
a = 1024; //把1024存放到变量a的地址中去
b = a; // 取变量a的值,赋值给b
===>在c语言中,任何一个变量,都有两层含义
(1)代表变量的存储单元的地址:变量的地址 ===> lvalue
(2)代表该变量的值: ===> rvalue
对于变量的访问,只有两种情况
read: 从变量的地址中取值
write : 把一个数值,写到变量的地址中去
问题:
如果我们知道了一个变量的地址,是不是我们就可以通过该变量的地址去访问这个变量
可以的
如何通过一个变量的地址去访问这个变量呢?
====> 指针
1.对象的访问方式
(1)直接访问:通过对象名去访问
直接访问有一个缺陷?
直接访问受对象的作用域的影响(限制)
// 局部变量不是永久的,函数返回的时候,局部变量就会被删除
(2)间接访问:通过对象的地址去访问,指针访问
只要你知道了对象的地址,就可以去任何地方访问它,不受作用域的影响
2.什么是指针?指针的概念
存储单元的地址:
分配给每一个变量的内存单元都有一个编号,这个编号就是我们说的存储单元的地址,
并且是按字节来编址。
在c语言中,指针的概念与地址的概念差不多,你可以认为指针就是一个地址,一个变量的地址
我们也称为变量的指针。
& 取地址符:
单目运算符, "取xxx对象的地址"
通过一个对象的指针去访问它,首先要解决 对象的指针(地址)的存储问题
这个时候需要定义一个变量来保存它的地址,这个变量我们称为"指针变量"
3.指针变量
什么是指针变量?
指针变量也是一个变量,也是用来保存数据的,只不过指针变量是用来保存其他对象的地址的。
如何定义一个指针变量?
回顾: 普通变量的定义
变量的类型 变量名;
在定义指针变量的时候,为了区分在指针变量的前面加一个*,来表示他是一个指针变量
====》
指针变量的定义:
指向对象的类型 * 对象名
"指向对象的类型":
指针变量指向的对象的类型,不是指针的类型。
"指向":
假如一个变量p保存了变量a的地址,
那么,我们就说p指向a
例子:
int a = 1024;
p = &a ; // 把a的地址赋值给p
// p保存了对象a的地址
// p 指向a
// p ---> 指针变量
这个p该如何定义?
指向对象的类型 * 指针变量名
typeof(a) * p
===> int * p;
练习:
char c[10];
char * p;
p = &c[9];
分析过程
p保存了c[9]的地址;
====> p指向c[9]
p是一个指针变量
4.与指针相关的运算符
& :取地址符
* :指向运算符
单目运算符
用法:
* 地址 <=> 地址对应的那个变量(对象)
int a;
* a // ERROR
例子:
int a = 1024;
*(&a) ===> *对象a的地址
===> 地址对应的那个对象
int b = *(&a); <==> b = a
so
*(&a) <==> *&a <==> a
* & ===>可以直接约掉
NOTE:
& *a <==> a ???? 不可以
& * 不能约
char ch ;
* (&ch) = 'A'; // *(&ch) ==>ch ,在此处代表的是变量ch的左值,"变量的地址"
char c = *&ch; // *(&ch) 在此处代表的是变量ch的右值,"变量的值"
代码分析:
int a = 5;
假如我们要定义一个变量p,来保存a的地址,
int *p;
p = &a;
// 把变量a 的地址赋值给p
p是一个指针变量
那么,它也有左值,右值
p的右值:p的存储单元中的内容 ===> &a
p的左值:p本身存储单元的地址 ===> &p
* p :在此处p代表 p的右值,p的值
* p <==>*(&a) <==> a;
a = 1024; <==> *(&a) ==>1024 ==> *p =1024
请大家写一个程序,来证明p的右值就是&a?
#include <stdio.h>
int main()
{
}
5.指针变量作为函数的参数
练习: 如下函数该这么设计?该怎么调用
void func(int *x,int *y)
{
int temp;
temp = *x; // ===> temp = *(&a)
*x = *y; // *(&a) = *(&b)
*y = temp; // *(&b) = temp
}
void func1(int *x,int *y) // 不可以的
{
int *temp;
temp = x;
x= y;
y = temp;
}
int main()
{
int a = 5;
int b = 6;
int *p = &a;
int *q = &b;
func(p,q); //调用这个函数的目的是:为了交换变量a和b的值
printf("a==%d\n",a);//6
printf("b==%d\n",b);//5
}
传的还是"值",传的还是"实参的值",
"实参的值可能是某个对象的地址"
例子:
void func(int *a,int *b)
{
int *t; //定义了一个指针t,但是t没有赋值,不知道这个t保存了
哪个对象的地址;不代表它里面没有值
如果是一个指针变量的话,不知道它指向的对象是谁
*t ==>t指向的那一个对象
如果我们去操作*t ,分为两种情况
read :
int m = *t; //有可能t指向的那一个对象不可读。
// 可能导致内存的非法访问 =>段错误
write:
*t = 1024;//有可能t指向的那一个对象不可写
// 可能导致内存的非法访问 =>段错误
*t = *a; //可能导致"段错误"
*a =*b;
*b = *t; //可能导致"段错误"
}
像这个例子中的t这样的指针,我们称之为"野指针"。
野指针:
指向一个未知单元(未知对象)的指针,称之为"野指针"
使用野指针,可能造成内存的非法访问 ===> 段错误
int *p;
// *p = 1024; 属于操作野指针
// int b = *p ; 属于操作野指针
int a;
p = &a; // 不属于操作野指针
空指针:
NULL
在计算机中,地址为0的存储空间是不存在的
如果一个指针的值为0(NULL),表示这个指针指向了
空(NULL),像这中指向0(NULL)的指针,我们称之为
空指针
int *p = NULL;
p它不是野指针
段错误原因:内存的非法访问
(1)数组越界,可能导致段错误
int a[10];
a[100] = 1024;
(2)非法使用指针(指针乱指)(野指针)
6. 数组与指针
数组元素与普通变量是一样的,也是有自己的地址。
数组元素也有左值和右值,并且数组元素的地址相邻的。
===>数组名可以代表首元素的地址(首地址)。
例子:
int a[5];
a是一个数组名, 数组名a当作指针来看: a <===> &a[0]
我要定义一个指针变量p,来保存a[0]的地址
该如何定义p?
int *p = &a[0] //OK
如果把数组名a当作指针来看, a ===> &a[0];
<===> p = a; // OK
a[0] = 1024;
<===> *p = 1024;
<===> *a = 1024;
p是指向a[0],那么能不能通过p去访问a[1]?
可以的
*p ===>*&a[0] ==>a[0]
a[0]和a[1]的地址是相邻的
p + 4 ==&a[1] ????不对的
p + 1 ===> &a[1]
通过指针p来访问a[1];
指针做加减的问题: int a[5]
p + i (p是一个指针,i是一个整数)
不是简单的加减值,而是加减i个指向单元的长度
p+1 ===> 往后面挪了一个指向单元的长度
p = > &a[0]
p+1 => 往后面挪了一个int单元的长度
(p+1) = >&a[1]
例子:
有p = &a[0],把数值100赋值给a[0],有多少种表示方法?
int a[10];
int *p = &a[0];
a[0] = 100;
*p = 100;
*&a[0] = 100;
*a = 100;
**&p = 100;
**&a = 100;
p[0] = 100;
*(p+0) = 100;
*(a+0)= 100;
*(&a[1]-1) = 100;
.....
练习:
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &a[2];
通过指针p去访问每一个元素的值,将其依次输出。
7.多维数组与指针
(1)在C语言中所有数组都是一维数组
(2)数组名可以当作指向第0个元素类型的指针常量,
并且数值上为第0个元素的首地址
假如有:
int a[3][4]
//int [4] a[3]
a[0] _ _ _ _
a[1] _ _ _ _
a[2] _ _ _ _
表达式 表达式的含义
a (1)当作指针
&a[0]
(2)代表整个数组
a[0] a[0]又是一个一维数组名
(1)代表为a[0]的整个数组
(2)当作指针
&a[0][0]
&a[0][0] 元素a[0][0]的地址
a+1 数组名a当作指针
==> &a[0] + 1
==> &a[1]
取整个一维数组a[1]的地址
&a[1] 取整个一维数组a[1]的地址
&a 数组名a只能代表整个数组
&a:取整个二维数组的地址
&a+1 数组名a代表整个数组
&a+1 : 往后面挪了整个二维数组a的长度(12个int)
a[1]+2 a[1]是一个一维数组名,只能当作指针
===> &a[1][0] + 2
===> &a[1][2] :元素a[1][2]的地址
*(a+1)+2 *(&a[0]+1)+2
==> *(&a[1])+2
==> a[1] + 2
==> &a[1][0] + 2
==> &a[1][2] :元素a[1][2]的地址
*(a[1]+2) a[1]是一个一维数组名,只能当作指针
==> *(&a[1][0] + 2)
==> *(&a[1][2])
==>a[1][2]:元素a[1][2]
*(*(a+1)+2) ==> *(*(&a[1])+2)
==>*(a[1]+2)
==>*(&a[1][0]+2)
==>*(&a[1][2])
==>a[1][2]:元素a[1][2]
指针常量:
指针本身不能够改变,但是指向的空间里面的内容是可以改变的
如: 数组名作为指针
int m,n;
int *const a = &m; // a就是一个指针常量
*a = 1024;// ok
a = &n ; // ERROR
常量指针:
是一个指向常量的指针,指向的对象是常量,那个对象是不能改变的
但是指针是可以改变的(也就是说可以保存其他的地址)
如: 字符串指针
int m = 1024,n;
const int *b = &m; ===> int const *a;
*a = 250;//ERROR
a = &n ; // ok
8.指针数组与数组指针
(1) 指针数组
指针数组是一个数组,只不过它里面的每一个元素都是相同类型的指针罢了!!!
定义数组:
数组元素的类型 数组名[元素个数]
例子:
int * p[4]; //指针数组
//定义了一个数组,数组名为p,里面含有4个元素
每一个元素类型都是 int *.
(2) 数组指针
数组指针是一个指针,只不过这个指针使用来指向一个数组的
(这个指针保存的地址是一个数组的地址罢辽!!!)
例子:
int a[4];
您能不能定义一个指针p来保存数组a的地址呢?
指针定义:
指向对象的类型 * p;
typeof(a) *p;
int [4] *p;
=>int (*p) [4];// p是一个指针,它指向的对象是一个int[4]类型的数组
练习:1.
int a[10] = {1,2,3,4,5,6,7,8,9,10};
printf("a = %p, a + 1 = %p, &a + 1 = %p\n",
a, a + 1, &a + 1);
......
2.分析如下程序的输出结果。
int a[5] = {1,2,3,4,5};
int * ptr = (int *)(&a + 1);
printf("%d %d\n", *(a + 1), *(ptr - 1));//2 5
int a[5] = {1,2,3,4,5};
int * ptr = (int *)&a + 1;
printf("%d %d\n", *(a + 1), *(ptr - 1)); // 2 1
int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//int[4] b[3]
//b[0] _ _ _ _
//b[1] _ _ _ _
//b[2] _ _ _ _
printf("**b = %d, *(b[0] + 1) = %d\n",
**b, *(b[0] + 1) );
**b : b当作一个指针
===>**(&b[0])
===>*b[0] //b[0]又是一个数组名,当指针来看
===>*(&b[0][0])
===>b[0][0]
*(b[0]+1):把b[0]当指针来看
===> *(&b[0][0]+1)
===>b[0][1]
- 有int b[3][4]; 假如要定义一个指针变量p,
来保存b[0][0]的地址,该如何定义?
//int[4] b[3]
//b[0] _ _ _ _
//b[1] _ _ _ _
//b[2] _ _ _ _
typeof(b[0][0]) *p;
====> int *p;
p = &b[0][0];
//typeof(p) ==> int *
p = b[0]; // ok
9.字符串指针
字符串是什么?
字符串就是一串字符,在c语言中是没有字符串类型的。
C语言的字符串是通过 char *(字符型指针)来实现的。
C语言的字符串,是用""(双引号)引起来的一串字符来表示。
并且字符串后面默认会加一个'\0',表示字符串结束的标志。
如;
"abcde" ==>5
"1" ==> 1
'123' 不是字符串,也不是字符啊
'\012' 不是字符串 ,是字符
"\012" ==>是字符串,有一个字符
"" ==>是字符串,空串
只需要保存字符串的首地址(首字符的地址)就可以了
从首地址开始找到第一个'\0',前面的这些字符就是字符串里面的字符
C语言中的字符串(如:"sssssss")保存在一个叫做.rodata(只读数据)的内存区域中。
如;
"12345"
在程序运行时,系统会为这个字符串在.rodata中分配6个字节大小的空间给它
"12345"的值,就是首字符的地址。
typeof("12345")
====》typeof(&'1')
====》typeof('1') *
====> const char *
例子:
char *p = "12345";
p保存的是字符串首字符的地址,&'1'
那么咱们是不是可以通过指针p来访问'1'呢
可以
因为: p = &'1';
char m = *p ; <===> m = '1'
*p = 'A';//ERROR 因为p指向的对象是一个常量,不可以改变的
字符串就相当于一个常量指针
问题:
字符'1'和'2'的地址是不是连续的呢?
是连续的
既然是连续的,那么咱们也可以通过指针p来访问'2'
p = &'1';
p+1 ===>往后面挪了一个char类型的单元长度 《===》 &'2'
*(p+1) ==> *(&'2') ===> '2'
printf("%c\n",*p); // 1
printf("%c\n",*(p+1));// 2
p+= 1; // OBJK
==>
p = p+1;
p = &'2';
*(p+1) = 'B';//ERROR
字符数组:
char s[5] ={'a','b','c','d','e'};
sizeof(s) == 5
char ch = *(s+1); <==> ch = s[1]
*(s+1) = 'B' ; // <===> s[1] = 'B'
s+= 1; //ERROR ==》 s = s+1 因为s是一个指针常量(数组名作为指针)为指针常量
char s[] = {'a','b','c','d','e'};
sizeof(s) == 5;
可以省略元素个数
char s[] = {"abcde"};
<===> char s[] = {'a','b','c','d','e','\0'};
sizeof(s) = 6;
*(s+1) = 'B' ; // 可以,因为s是一个字符数组,数组区域是可读可写的
printf("%s\n",(s+1)); //Bcde
%s -> char *
把后面的哪个地址(指针)当作是一个字符串的首地址,一个一个字符的输出,
直到遇到'\0'结束,'\0' 不打印
练习;
1.分析如下程序的输出结果
p = "abcde";
printf("%s\n",p);//abcde
char s[] = "12345";
s[1] = 'B';
printf("%s\n",s);//1B345
2.写一个函数,用来求一个字符串的长度(包含了多少个有效字符)
(1)确定函数名
My_Strlen : 用来求一个字符串的长度
(2)确定参数列表(形参列表)
const char *s
(3)确定返回值的类型
返回值:
有效字符的个数(int)
(4)代码的实现
int My_Strlen(const char *s)
{
int count = 0;
while(*s) // *s !='\0'
{
count ++;
s++;
}
return count;
}
int main()
{
char q="123\0a0345";
scanf("%s",q);
// int a ;
// a = My_Strlen();
// printf(a)
printf("%d\n",My_Strlen((const char *)q));
}
10.几个常用的字符串的处理函数(标准库里面的)
(1)strlen :用来求一个字符串的长度
#include <string.h>
int strlen(const char *s);
@s : 要计算长度的那一个字符串的首地址
const char * 类型: 表示在程序运行的过程中,指针指向的字符串不能被修改
返回值: 返回字符串的有效字符个数(不包括'\0');
例子:
strlen("abcde") == 5
char s[4] = {'1','0'};
sizeof(s) == 4
strlen(s) == 2
strlen("abcd\nabc") == 8
strlen("123\123abc\0abc") == 7
strlen("123\01a3abc\0abc") == 9
strlen("123\000abc") ==3
strlen("123\0x123") == 3
strlen("123\x123456gad") == 7
(2)strcpy/strncpy
字符串拷贝函数
NAME
strcpy, strncpy - copy a string
SYNOPSIS
#include <string.h>
char *strcpy(char *dest, const char *src);
strcpy: 用来把src指向的字符串,拷贝到dest指向的空间中去,直到遇到'\0'结束。
@dest: 目的地(dest必须是一个可写的空间)
@src : 从哪里来
返回值:
返回拷贝之后,目的地字符串的首地址
例子:
char s[6];
strcpy(s,"12345");
printf("%s\n",s); //12345
char *p = "abcde";
char *q = "12345";
strcpy(p,q); //ERROR
strcpy 有一个小小的BUG!!!
因为他没有考虑到数组越界的问题,有可能会导致内存的非法访问
char *strncpy(char *dest, const char *src, size_t n);
strncpy: 为了解决strcpy的这个bug的情况,功能是类似的,只不过
它顶多拷贝n个字符到 dest
到底拷贝多少字符?(<=n)
(1)遇到了\0拷贝结束的,此时\0也会被拷贝
(2)已经拷贝了n个字符(后面的\0不会自动拷贝,除非最后一个字符是\0)
例子:
char s[10];
strncpy(s,"1234567890",10);
strncpy(s,"123",10);
练习:
char s1[8]
char s2[8] = {"ABCDE"};
strncpy(s1,"123456789",8);
printf("%s\n",s1);
printf("%s\n",s2);
(3)strcmp/strncmp
字符串的比较函数
那么字符串该如何比较勒?
一个一个字符进行PK对应的ASCII的值
if c1 >c2
返回 >0
if c1 < c2
返回 < 0
if c1 == c2
则继续比较下一个字符,
如果全部相等则返回0
strcmp("123","ABC"); <0
strcmp("123","123\0ABC"); ==0
strcmp("1234","123"); >0
char *s = "GONG";
strncmp(s,"GONGJIANWEN",4) ==0
#include <string.h>
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
strncmp它的功能与strcmp类似,
只不过它只比较s1,s2前面的n个字符
(4)strcat/strncat
NAME
strcat, strncat - concatenate(连接) two strings
SYNOPSIS
#include <string.h>
char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n);
strcat: 用来把src指向的字符串,拷贝到dest指向的字符串的末尾
@dest :指向目标字符串(一段可写的空间)
dest指向的存储空间必须要足够的大,why?(越界)
@src :指向原始字符串(表示将要被拷贝的那一个字符串)
返回值:
如果成功,返回连接后的字符串的首地址dest
如果失败,返回NULL
例子:
char s1[12] = {"ABCD"};
strcat(s1,"12345");
printf("%s\n",s1);//ABCD12345
当然这个函数也有一个小小的BUG,你懂的
so strncat 就是用来修复strcat的这个bug的
strncat :
把src指向的字符串拷贝到dest指定的字符串的末尾
但是最多拷贝n个字符
(1)遇到\0拷贝结束,此时\0也会被拷贝
char s1[12] = {"ABCD"};
strncat(s1,"12345",8);//ABCD12345
(2)如果要是没有遇到\0,但是以及拷贝了n个字符了,也结束
char s1[12]={"ABCD"};
strncat(s1,"123456789",8); //ABCD12345678
练习:
分析如下程序的输出结果
char s[12];
strcat(s,"12345");
printf("%s\n",s); //随机的值
写一个函数,把一个十进制的数字字符串,转成一个整数
"12345"
=>12345
"-12345"
=>-12345
"+12345"
=>12345
/*
my_atoi : 将一个十进制的数组字符串,转换成一个整数
@s :指向十进制数组字符串的 const char *s
返回值: 整数值
*/
int my_atoi(const char *s)
{
int minux = 0; //符号 0 --负数 1 -- 正数
int d = 0; // 保存当前位的数值
int num = 0; //整数的值
if(*s == '-')
{
minus = 0;
s++;
}
else if(*s == '+')
{
minus = 1;
s++;
}
else
{
minus = 1;
}
while(*s) // *s!='\0'
{
d = *s-'0';
num = num *10 +d;
s++;
}
if(minus==0)
{
num = num *(-1);
}
return num;
}
int main()
{
char s[256];
scanf("%s",s);
printf("%d\n",my_atoi((const char *)s);
//printf("%d\n",atoi());
}
其实呢,atoi这个函数是标准库中的一个函数,大家是可以直接去用的,
NAME
atoi, atol, atoll - convert a string to an integer
SYNOPSIS
#include <stdlib.h>
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
在c语言中,不仅是变量,数组有地址,其实我们的函数也是有地址的。
只要是有地址的东西,那么我们就可以定义一个指针变量去保存这个地址
并且可以通过这个这个指针去访问指向的对象。
函数地址 ----> 函数指针
11.函数指针
什么是函数指针?
函数指针也是一个指针,只不过这个指针指向的是一个函数
也就是说这个指针保存了函数的地址
(1)函数指针该如何定义
指针定义:
指向对象的类型 * 指针变量名;
函数的类型如何表述?
函数的类型三要素:
函数的返回值类型 (函数的参数类型列表)
例子:
int sum(int a,int b);
int (int , int)===>这个是一个类型,是用来描述一个类似与sum函数的!
int * abc(int a,float b)
{}
描述abc的类型:
函数的返回值类型 (函数的参数类型列表)
int * (int,float)
====> 是一个返回int*型,带一个int和float的函数类型
定义一个指针变量q,用来保存abc的地址:
指向对象的类型 *q;
typeof(abc) *q;
===>int * (int ,float) *q;
===>int * *q(int , float);
===>int * (*q)(int,float);
需要定义一个指针变量p,来保存函数sum的地址,该如何定义呢?
int (*p)(int,int);
指向对象的类型 *p;
typeof(sum)* p;
===>int (int,int)* p;
===>int (*p)(int ,int);
函数指针的定义方法;
指向函数的返回值类型 (*指针变量名)(指向函数的形参类型列表);
例子;
- char ** func(void)
{}
请大家定义一个函数指针变量p,来保存函数func的地址
char ** (*p)(void);
2.请各位大佬定义一个指针变量q,来保存如下函数的地址
数组名a作为函数的参数,是当作指针来看的
=> &a[0] typeof(&a[0]) ==> int *
//int arr_sum(int * a,int n)
int arr_sum(int a[],int n);
{}
=> int (*q)(int *,int);
(2)该怎么将函数的地址赋值给函数指针变量呢?
p -> 函数指针
p = 函数的地址
函数的地址怎么去获取呢?
&对象名 ==> 取对象的地址
函数的地址:
&函数名
or
函数名:在c语言中,函数名本身就代表函数的首地址
例子:
int sum_array(int *a,int n)
{}
//定义一个函数指针p
int (*p)(int *,int);
//将函数sum_array的地址赋值给p;
p = &sum_array;
//or
p = sum_array;
//此时p指向函数sum_array
(3)怎么通过函数指针取访问这一个函数呢?
函数调用:
函数名(实参列表);
a,
p = sum_array;
sum_array(a,5);
<==> p(a,5);
b,
p = &sum_array;
*p = *&sum_array;
sum_array(a,5);
===>(*p)(a,5);
结论: 通过函数指针去调用指向的函数,有两种方式
p为函数的指针
(1)p(实参列表)
(2)(*p)(实参列表)
练习;
1.首先写一个函数用来求一维数组中所有元素之和
然后在main中定义一个指针p,通过p去调用sum_array.
#include <stdio.h>
//int sum_array(int a[],int n)
int sum_array(int *a,int n)
{
int i,sum=0;
for(i=0;i<n;i++)
{
sum += a[i];
}
return sum;
}
int main()
{
int m[10] = {0,1,2,3,4,5,6,7,8,9};
int (*p)(int *,int);
p = &sum_array;
int n = *p(m,10);
printf("%d\n",n);
}
12.数组作为函数的参数的问题
数组作为函数的参数的话,数组名都是当作指针来看!!!
把数组作为函数的形参:
一维数组
a,数组元素类型 数组名[] , 元素个数
b,数组元素类型 * 指针变量名 ,元素个数
二维数组
a,数组元素类型 数组名[][列数] ,行数
b,数组元素类型 (*指针变量名)[列数],行数
练习:
首先写一个函数(sum_2_array)用来求二维数组中所有元素之和,
然后,在main中定义一个函数指针q,通过q来调用sum_2_array.
int sum_2_array(int (*a)[4], int m)
{
int i, j ;
int sum = 0;
for(i = 0; i < m; i++)
{
for(j = 0; j < 4; j++)
{
sum += a[i][j];
}
}
return sum;
}
int main()
{
int m[10] = {0,1,2,3,4,5,6,7,8,9};
//定义一个函数指针p,来保存sum_array
int (* p)(int *, int );
//将函数的地址赋值给 p
p = &sum_array; //p = sum_array
//通过p来调用sum_array函数
int n = p(m, 10);
printf("%d\n", n);
int x = (*p)(m, 10);
printf("%d\n", n);
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//定义一个函数指针q,来保存sum_2_array
int (*q)(int (*)[4], int);
//将函数的地址赋值给 q
q = &sum_2_array; //q = sum_2_array
//通过函数指针q来调用它所指向的那一个函数
int y = q(a, 3);
//int y = (*q)(a, 3);
printf("%d\n", y);
}
保存普通变量地址的 变量 ==> 指针变量
指针变量也有地址,那么保存指针变量的地址的 变量 ==> 二级指针
13.二级指针以及多级指针
int a= 1024;
可以定义一个指针变量p来保存a的地址
int *p;
p = &a; //p指向a
p本身也有地址,我们可以定义一个指针变量p2来保存p的地址:
typeof(p) *p2;
int * *p2;
// p2二级指针,它保存的是一个一级指针的地址
//要分清楚到底是几级指针,怎么去区分?
// QTMD,你只要知道他是一个指针,(保存的是谁的地址)就可以了
p2 = &p; //p2指向p
**p2 = **&p = *p = *&a = a
14.main 函数的参数的问题
int main()
{}
在linux下面,程序运行的时候,可以带参数的,只不过所有的参数都当作是字符串来处理
如:
"./a.out" "123""456""789"......
参数多个字符串 ==>字符串的数组
char * argv[] ={"./a.out","123","456","789"};
在运行main函数的时候,可以把上面的那个数组传递给main函数的
so
在linux下面的c语言程序的main的参数可以有如下定义:
元素个数,数组元素类型 数组名[]
or
元素个数,数组元素类型 * 指针变量名
==》
main函数的写法:
int main(int argc,char *argv[])
{}
or
int main(int argc,char **argv)
{}
==》
在程序运行时,给main函数传递的参数,它在main函数中是如何表示的呢?
./a.out 123 456
int main(int argc,char *argv[])
{
//char *argv[] = {"./a.out","123","456","789"};
//int argc = 4;
//argv[0] -> "./a.out";
//argv[1] -> "123";
//argv[2] -> "456";
//argv[3] -> "789";
}
练习:
- 请大家写一个程序,将main函数的参数一个一个全部输出,
并且打印main的参数个数
int main(int argc,char **argv)
{
printf("argc==%d\n",argc);
int i;
for(i=0;i<argc;i++)
{
printf("argv[%d]=>%s\n",i,argv[i]);
}
}
- 请各位大佬们,输入两个整型数据字符串,
完成两个整数相加
./a.out 123 456
==> 579
/*
if(argc != 3)
{
printf("Input error\n");
return 0;
}
int sum = atoi(argv[1]) + atoi(argv[2]);
printf("sum == %d\n", sum);
*/
15.动态内存分配函数
main()
{
int n;
scanf("%d",&n);
int a[n]; // 动态数组,根据用户的输入,数组元素的个数动态改变的。
//上述方法,在一些编译器中可能不被认可
// 那么,咱们还有没有其他的办法呢?
}
malloc/realloc/calloc
free
NAME
malloc, free, calloc, realloc - allocate and free dynamic memory
SYNOPSIS
#include <stdlib.h>
malloc: 用来动态分配一个size大小的空间,并且把分配到内存空间的首地址返回。
malloc分配的空间的生存期,是随进程持续性。malloc分配的空间一旦分配
给你,它就不会自动释放,一定要你自己去调用free 或 你的进程消亡了。
void *malloc(size_t size);
@size:
要分配的内存空间的大小
返回值:
成功返回分配的内存空间的首地址
失败返回NULL。
例子:
- int a[n];
//开辟了sizeof(int) * n 连续的内存空间
//现在相当于a数组名当作指针来看,a指向了n*sizeof(int)的连续的内存空间
<=> int *a =(int *)malloc(n * "乘号" sizeof(int))
// 现在a就相当于是有n个int类型元素的数组啦。
// a就相当于数组名
- char *p = (char *)malloc(100);
// p指向了一个100字节的内存空间
// 通过p指针操作去访问这段空间。
char ch;
p = &ch;
// p指向ch,那么,上面100字节的空间就没有任何指针指向它了
//这100个字节的内存是不是就访问不了,但是这段内存他是存在的。
//并且也不能够去分配给别人。。这样的内存我们称之为垃圾内存。
// 把这样的一种现象称之为"内存泄露"
malloc 可能会导致内存泄露(产生一些垃圾内存)
比如说,上述的例子
free:用来释放ptr指向的内存的
void free(void *ptr);
@ptr:必须是malloc/realloc/calloc 这三个函数申请的内存
释放后的这部分数据可能存在并且维持原来的值,也有可能被清空
或者也有可能被修改为了其他的值。释放之后,本质上来说,是不被
允许去访问那块内存了,因为那块内存不属于你了,但是有些编译器
还是允许去访问,但是呢访问的结果是不确定的。
so,建议大家 将内存free掉了之后,立马将那个内存的指针指向NULL。
calloc : 作用类似与malloc,也是用来分配动态内存空间的
不过他是数组分配函数,既然是数组,那么这个函数带有两个参数
void *calloc(size_t nmemb, size_t size);
@nmemb : 表示分配多少个数组元素
@size :表示每一个元素占多少个字节
so,它总共分配的连续空间大小为:nmemb * size
calloc(n,size) <==> malloc(n*size)
例子:
int a[10];
==> int *a = (int *) calloc (10,sizeof(int));
==> int *a = (int*)malloc(10*sizeof(int));
char ch[5];
==>char *ch =(char*)calloc(5,sizeof(char));
==>char *ch =(char*)malloc(5*sizeof(char));
realloc: 用来把ptr指向的动态内存,拓展到size大小
void *realloc(void *ptr, size_t size);
@ptr: 由malloc/realloc/calloc 返回的动态内存的地址
@size: 将原来的空间,拓展到size大小
ptr = NULL
realloc(NULL,size) <=> malloc(size)
ptr != NULL
拓展
1.size 》 原来大小
realloc 用来把ptr指向的内存,扩展到size字节,原来的内存内容保持不变,
后面新增的内容不会初始化
2.size == 0
realloc(ptr,0) <===> free(ptr)
3.size < 原来大小
这种情况,作者都没有考虑,这种行为结果是未定义的(什么结果都可能会发生)
练习: 咱们在一个函数中,值可以有哪些方式来传递?
(1)对象的指针
(2)通过函数的返回值
(3)全局变量
请大家设计一个函数,分配指定的内存块(100bytes)
#include <stdio.h>
char *q = NULL;
void func_1(void)
{
q = (char*)malloc(100);
}
int main()
{
char *p =NULL;
//调用func函数,目的是让p指向100bytes的首地址
func_1();
p = q;
free(p);
}
练习:
1.实现strncpy函数
char * my_strncpy(char *dest ,const char *src , int n )
{
int i ;
for(i=0;i<n&&src[i]!='\0';i++)
{
dest[i] = src[i];
}
while(i<n)
{
dest[i] = '\0';
i++;
}
return dest;
}
2.实现strcmp函数
#include <assert.h>
int my_strcmp(const char * str1,const char * str2)
{
//assert :断言
assert(str1!=NULL && str2 != NULL);
int ret =0;
while(!(ret = *str1-*str2))
{
str1++;
str2++;
}
if(ret > 0)
{
ret = 1;
}
else if(ret<0)
{
ret = -1;
}
return ret;
}
3.n个人围成一圈,编号从1到n,从编号为1的人开始报数,报到m的人离队,
下面的人接着从1开始报数,依次下去,写程序求最后剩下一个人的编号!
/*
Baoliu_num : 剩下的一个人的编号是几
@a : 数组名
@n : n个人
@m : 报道m离队
返回值: 返回剩下的那一个人的编号
*/
int Baoliu_num(int *a,int n,int m)
{
int out= 0; //记录已经离队多少
int count = 0; // 记录当前的报数
int i;
int *p = a;
for(i = 0;i<n;i++)
{
*(p+i) = i+1;
}
i = 0; //i为目前报道第几人
while(out<n-1)
{
if(*(p+i)!=0)
{
count ++;
}
if (count == m)
{
coun t =0;
*(p+i) = 0;
out++;
}
i++;
if(i==n)
{
i = 0;
}
}
for(i=0;i<n;i++)
{
if(*(p+i)!=0)
{
printf("%d\n",*(p+i));
break;
}
}
}
- 分析如下代码是否正确,为什么?
(1)
int main()
{
char a;
char * str = &a;
strcpy(str, "hello");
printf("%s\n", str);
return 0;
}
错误的,在调用strcpy的时候,出现了越界,一旦越界就可能导致段错误
造成"段错误",后面的程序就不会被执行,没有输出结果。
(2)
char * GetMemory(void)
{
char p[] = "hello world";
return p;
}
int main()
{
char * str = NULL;
str = GetMemory();
printf("%s\n", str); //
}
编译错误:数组是不能进行返回
返回的是指向"栈空间"的指针,函数结束,栈空间内存已经被收回,
那么那块内存可能就不能够被访问了,而且内存的内容是不可知的。
(3)
char * s = "AAA";
printf("%s", s);
s[0] = 'B';
printf("%s", s);
因为"s[0]='B'",s指向的是一个 .rodata的只读空间
不能进行写的操作
(4)
swap(int * p1 , int * p2)
{
int * p ;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
p是一个野指针,后面的操作都是在操作野指针,
操作野指针,可能会导致内存的非法访问,出现段错误
(5)
下列程序的输出结果是?(5)
int main()
{
char a[10]={9,8,7,6,5,4,3,2,1,0},*p=a+5;
printf("%d",*--p);
}
(6)下列程序的运行结果是? (3 6)
void fun(int *a,int *b)
{
int *k;
k=a;
a=b;
b=k;
}
int main()
{
int a=3,b=6,*x=&a,*y=&b;
fun(x,y);
printf("%d %d",a,b,);
}
(7)以下程序的输出结果是?(7)
#include <stdio.h>
#include<string.h>
main()
{
char b1[8]="abcdefg",b2[8],*pb=b1+3;
while(--pb>=b1)
strcpy(b2,pb);
int l=strlen(b2);
pritntf("%d\n",strlen(b2));
}
(8)若有一些定义和语句,错误的是? (A)
#include <stdio.h>
int a=4,b=3,*p,*q,*w;
p=&a;q=&b;w=q;q=NULL
A,*q=0 B,w=p; C,*p=a D,*p=*w;
(9)以下程序的输出结果是? (空格)
int main()
{
char *p="abcdefgh",*r;
long *q;
q=(long*)p;
q++;
r=(char*)q;
printf("%s\n",r);
}