C语言基础———指针,结构体指针,函数指针

指针基础

一 指针就是地址,地址就是指针.指针存储的是内存的地址.
二 指针的定义
1.&表示取址运算符,&a 可以取到 a 在内存中的地址;打印地址的占位符为(%p),printf(“%p\n”,&a);
2. 指针变量的定义
指针变量是用来存放地址的.
指针定义的格式:
类型数据  *变量名  =  初始值;
int *p = NULL;*在这里表示p 是一个指针变量,不是运算符.
printf(“%p\n”,p); 在打印指针的时候不需要使用取址运算符(&),因为指针本来就是地址.
*p 指针为空时,打印出来0x0的地址,为空.
int  a = 10;
p = &a;
printf(“%p\n”,p);
访问内存中的数据有两种方法:
1.直接访问内存中的数据.
printf(“%p\n”,a);
2. 通过指针进行访问.
*也可以作为取值运算符.* p 代表取到 p 指向内存中存储的数据  printf(“%d\n”,*p);
int a = 10,b = 20;
int *p1 = &a;
int *p2 = &b;
对指针重新赋值,意味着指针重指向,也就是说指针指向一个新的地址.
p1 = p2;p1指向 c 的首地址
printf(“*p1 = %d,* p2 = %d\n”,*p1,*p2);

int a = 15,b = 20;
int *p = &a;
int *p1 = &b;
单独的* p 表示取值,* p = 11 表示向 p 指向的内存中存入数据.
*p = 11;
printf(“%d\n”,a);

int *p3 = &a;
p3 = p1;
*p1 = 8;
printf(“a = %d,b = %d\n”,a,b);a = 11,b = 8

经典练习
int a = 6,b = 8,c = 10;
int *x = &a;
x = &c;
int *y = &b;
int *z = &c;
*z = 5;
*x = 12;
x = y;
 *y = *x + *z;
printf(“a = %d,b = %d,c = %d\n”,a,b,c);

指针中常见的问题
1.内存编号比较小的存储单元,是由系统控制的,不允许访问.
int *p = NULL;
*p = 100;   error
2.野指针,定义时没有赋初始值,操作野指针是很危险的
int *p;
*p = 100;error
3.定义指针时,*怎么写?
int *p = NULL;推荐
int* p1 = NULL;
int *p1 = NULL;
int*p3 = NULL;
4.指针变量在内存中占几个字节
与数据类型无关,在32位操作系统下,指针所占字节数是4位,在64位操作系统中,指针所占字节数是8位.
printf(“%lu\n”,sizeof(long *));
5. 修饰指针变量数据类型的作用
a. 指针变量数据类型,决定*(取值运算符),取多少个字节的数据;
定义指针的数据类型一定要和指向数据的数据类型一致,这样才能把数据完整的取出来
int *p = NULL;
int a = 268;
p = &a;
printf(“%d\n”,*p);
char *c = NULL;
c = &a;
printf(“%d\n”,*c);
b.指针类型,决定指针变量加1操作时,跳转多少个字节

int *p = NULL;
printf(“%p\n”,p); //0x0
printf(“%p\n”,p+1);//0x4
printf(“%p\n”,p+2);//0x8
printf(“%p\n,p++);//打印的结果是:0x4   但是实际结果是:0x8

p+1 和  p++或++ p 的关系
相同:取到的都是下一个字节的地址
不同:++p和 p++ 造成指针的重指向
6.指针在数组中的应用
int a[5] = {1,2,3,4,5};
数组名就是数组元素的首地址

printf(“%p\n”,a);
printf(“%p\n”,&a[0]);
printf(“%p\n”,p);

printf(“%p\n”,a+1);
printf(“%p\n”,&a[1]);
printf(“%p\n”,p+1);

printf(“%p\n”,a+2);
printf(“%p\n”,&a[2]);
printf(“%p\n”,p+2);

printf(“%d\n”,*a);
printf(“%d\n”,*(a +1));
printf(“%d\n”,*(a+2));

用指针遍历数组
for(int i= 0;i < 5;i++){
printf(“%d\n”,*(a+i));
}
定义指针变量访问数组中的元素
int *p =NULL;
p = a;
printf(“%p\n”,a);
printf(“%p\n”,p);
printf(“%d\n”,*p);

printf(“%d\n”,*(p+1));
printf(“%d\n”,*(p+2));

练习
10个元素的数组,通过指针变量为元素随机赋值[22 33]

7.数组名(数组元素的首地址)   与指向数组首地址的指针变量的区别:
a. 数组名(数组首地址) 是一个常量地址不能被修改
a = NULL;error
指针变量可以修改,可以重指向
b. 数组名(数组的首地址),用sizeof 计算得到的结果,整个数组所占的字节数,而用 sizeof 计算指向数组元素首地址的指针变量得到是一个定值4或者8
printf(“%lu\ n”,sizeof(c));

//定义一个10个元素的数组,通过指针变量为元素随机赋值[22 33],冒泡排序;
8.指针在字符串中的应用
用% s 打印是从指针指向那个地址开始向后输出
char string[] = “Canglaoshi”;
printf(“%s\n”,string);

printf(“%s\n”,string+1);
通过指针访问里面的元素
数组名(数组元素的首地址)  == 指针
*(string + 3) = ‘\0'
printf(“%s\ n”,string);
 通过指针访问元素
printf(“%c\ n”,*(string +3));

通过指针计算字符串的长度
int i= 0;
*(string +i)  取到数组中对应位置的值
while(*(string+i) != ‘\0’){//string[i]
i++;
}
printf(“%d\n”,i);

指针变量一定要和指向的数据类型相同

char *p = string;
int i = 0;
while(*(p+i) != ‘\0’){
i++;
}
char string[] = "canglaoshi I love you";//将字符串的首个字母大写,并且将空格换成下划线
char string[] = "canglaoshi I love you";
   *string -= 32;
   char *p = string;
   if (string[0] >= 'a' && string[0] <='z') {
   string[0] -= 32;
   }
   int i = 0;
   while (*(string+i) != '\0') {
   if (*(string + i) == ' ') {
   *(string + i) = '-';
   }
   i++;
   }
   printf("%s\n",string);

 //写一个函数实现两变量的交换
   int a = 10;
    int b = 20;
    change(&a,&b);
    printf(
"a = %d,b = %d\n",a,b);
   
   
   
int a = 10,b = 20;
   
int sum = 0,sub = 0;
    sum =
sumAndsub(a,b,&sub);
   
printf("sum = %d,sub = %d\n",sum,sub);
   
   
float a[10] = {0};
   
for (int i = 0; i < 10; i++) {
        *(a+i) =arc4random()%
21+10;
        printf(
"%.2f ",*(a+i));
    }
   
printf("\n");
   
for (int i = 0; i < 10 - 1; i++) {
       
for (int j = 0; j < 10 - i - 1;j++) {
//            if (a[j] > a[j + 1]) {
//                float temp = 0;
//                temp = a[j];
//                a[j] = a[j + 1];
//                a[j + 1] = temp;
//            }
           
           
if (*(a + j) > *(a + 1 + j)) {
               
float temp = 0;
                temp = *(a +j);
                *(a + j) = *(a + j +
1);
                *(a + j +
1) = temp;
            }
        }
    }
    
printf("\n");
   
for (int i = 0; i < 10; i++) {
        printf(
"%.2f ",*(a+i));
    }

结构体指针
1.形参和实参的区别
a. 形参:定义的时候写的参数,(起一个描述的作用,没有实际的意义)
b. 实参:函数实际调用时传进来的参数
形参和实参的传递是单向的.
2.结构体嵌套
在定义结构体的时候,结构体的成员变量也可以是结构体.
3.结构体指针
指向结构体的指针叫结构体指针.
数据类型  + *  + 变量名 + 初始值;
Student  stu1 = {“lisi”,’M’,29}; 
 指针变量指向结构体变量的首地址,相当于指向结构体第一个成员变量的地址
Student *p = &stu1;
结构体指针的访问
printf(“%s”,(*p).name);
 通过指针变量访问结构体变量的成员
printf(“%s,%c,%d\n”,p->name,p->sex,p->age);
4——>:指向操作符
只有定义的是结构体变量的首地址,才可以使用(—>指向操作符)访问结构体变量
printf(“%s,%c,%d\n”,p->name,p->sex,p->age);
5.float x1 = 0.1;
float x2 = 1.;
float x3 = 1.f;
float x4 =1.0f;
float x5 = 1;
6.结构体数组与指针的关系
结构体数组的数组名是一个结构体指针常量.
int a[5] = {1,2,3,4,5};
int *p = a;//数组名就是数组的首地址.
printf(“%d\n”,*p);
*stu   :   stu[0]  //代表的都是数组中第一个元素,
*(stu + 1) :stu[1]  // 代表的都是数组中的第二个元素.
*(stu + 2)   :stu[2]  //代表的是数组中的第三个元素.

访问数组中结构体元素的属性
*stu.name   :stu[0].name
(*(stu + 1)).name  :  stu[1].name

—>:指向操作符  :使用的时候一定要保证指针指向结构体变量的首地址
stu—> name   :stu[0].name
(stu + 1) —>name  :stu[1].name
(stu + 2)——>name   : stu[2].name


7.遍历结构体数组的时候,需要向函数中传入数组和长度.
8.指针数组(也就是指向指针的指针)
int  a = 3,b = 4,c=  5;
数组中的元素都是指针(地址)数组,就是指针数组.
int *array[3] = {&a,&b,&c};//3*8个字节
printf(“%p\n”,array[0]);
printf(“%p\n”,&a);

printf(“%d\n”,**array);//*array  取到地址  **array  取到地址里的值
printf(“%d\ n”,*(*(arrary + 1)));
二.函数指针
函数指针的声明方法为:
函数类型  (标示符  指针变量名 )(形参列表);
函数类型说明函数的返回类型,”(标识符 指针变量)”中的括号不能省,若省略整体则成为一个函数说明,说明了一个返回的数据类型是指针的函数,后面的”形参列表"表示指针变量指向的函数所带来的参数列表.
例如:
int func(int x); 声明一个函数
int (*f)(int x); 

函数指针

1.函数指针类型的确定步骤
1⃣把函数名换成(*)    void (*)()    int(*)(int,int)
2⃣如果有参数,把参数名去掉,只保留参数类型  void (*)()   int (*)(int,int)
3⃣格式
指针类型  *指针名  = 初始值;
char *p = NULL;
函数指针的类型
返回值类型(*函数指针变量名)(参数类型1,参数类型2.....) = 初始值;
函数后面的()是函数调用符.
注意:  函数指针变量名不能和指针重名
int sum(int a,int b){
return a+b;
}
int (*Sum)(int,int);
Sum = sum;
int y = Sum(3,5);
printf(“%d\n”,y);
同一个函数指针可以指向不同的函数,但是(前提是:函数指针的类型必须一致)
2.typedef 原始类型  新类型;
给函数指针类型起别名
typedef int (*FUN)(int,int);
typedef void (*HELLO)
typedef  返回值类型(*新类型名)(参数类型1,参数类型2.....)
3.函数回调
函数回调:就是用函数指针来调用函数
函数名就是指针,存放的函数的首地址

4.动态排序
就是不断的改变排序的条件,根据不同的条件,调用不同的函数,进行排序.

5.枚举
定义:一组有符号的整形常量,一一列举所有的状态,罗列出所有可能的结果
enum season {
spring,
summer,
autumn,
winter};
printf(“%d\n”,spring);
枚举经常和 swith 合用
枚举间接提高了代码的可读性
将人能识别的表示符,与计算机能识别的数字建立联系
6.宏只做简单的替换,在预编译的时候完成替换
宏命名规范:
1⃣纯大写
2⃣k +  大驼峰
无参宏直接进行定义,有参宏可以使用常量表达式
#define SUM(A,B)  ((A) - (B)) // 最安全的方式
有参数的宏,只做简单的替换运算,不会判断运算符的优先级
int p = SUM(3 + 7,5 + 6) * 3;//3 + 7 * 5 + 6

7.条件编译
第一种
#ifdef  MY
printf(“Hello Word!!");
#else
printf(“明天考试!!");
#endif

特点:如果标示符被# define 过,就会执行代码段1,否则就会执行代码段2

第二种方式
 #ifndef  标示符
     代码段1
     #else
     代码段2
     #endif
   特点:如果标示符没有被#define过,就会执行代码段1;如果被定义过,,就编译代码段2

第三种方式
#if  常量表达式
代码段1
#else
代码段2
#endif

#if MY
printf(“你好蓝欧!!");
#else 
printf(“你好中国!!");
#endif

特点:如果常量表达式非0,就执行代码段1,否则执行代码段2


8.const 常量修饰符
被 const修饰的常量是不能重新赋值的
































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章