嵌入式0基础开始学习 ⅠC语言(7)指针

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]

  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);

函数指针的定义方法;

指向函数的返回值类型 (*指针变量名)(指向函数的形参类型列表);

例子;

  1. 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";

}

练习:

  1. 请大家写一个程序,将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]);

}

}

  1. 请各位大佬们,输入两个整型数据字符串,

完成两个整数相加

./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。

例子:

  1. int a[n];

//开辟了sizeof(int) * n 连续的内存空间

//现在相当于a数组名当作指针来看,a指向了n*sizeof(int)的连续的内存空间

<=> int *a =(int *)malloc(n * "乘号" sizeof(int))

// 现在a就相当于是有n个int类型元素的数组啦。

// a就相当于数组名

  1. 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. 分析如下代码是否正确,为什么?

(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);

}

相关推荐
XH华5 小时前
初识C语言之二维数组(下)
c语言·算法
Uu_05kkq8 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
嵌入式科普10 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A10 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
1 9 J11 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
仍然探索未知中13 小时前
C语言经典100例
c语言
爱吃西瓜的小菜鸡13 小时前
【C语言】矩阵乘法
c语言·学习·算法
Stark、14 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
deja vu水中芭蕾15 小时前
嵌入式C面试
c语言·开发语言
stm 学习ing16 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl