c基础(二)

本文详细介绍了指针在C语言中的基本概念、运算符、初始化、内存地址获取、函数参数传递、内存分配与释放(malloc、calloc、free、realloc)、内存复制(memcpy、memmove)以及内存比较(memcmp)。同时涵盖了restrict关键字的使用,帮助读者掌握C语言中关键的内存操作技巧。
摘要由CSDN通过智能技术生成

指针:

  含义:是一个值,一个值代表着一个内存地址,类似于存放路径   

* 运算符 :

  1 字符*表示指针

作用:通常跟在类型关键字的后面,表示指针指向的是什么类型的值
int * foo, * bar;


声明指针后会随机分配一个内存空间,指针指向的数据是随机的

指针的初始化:
int* p;
int i;

p = &i;
*p = 13;

未初始化的指针数据最好这样定义:int* p = NULL;

 2 运算符

作用:用来取出指针变量所指向的内存地址里面的值
* foo  取出定义的指针的值

/*注意,数组名指向的地址是不能更改的*/

& 运算符:

作用:用来取出一个变量所在的内存地址

int i = 5;

if (i == *(&i)) // 正确,*(&i)含义:先取出i的内存地址,在获取内存地址对应的值

 运算:

     1 指针与整数值的加减运算

           指针与整数值的运算,表示指针的移动。

     2 指针与指针的加法运算

          两个指针进行加法是非法的

     3 指针与指针的减法

          相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位。

          高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值

      返回值:ptrdiff_t类型,一个带符号的整数类型别名

     4 指针与指针的比较运算

        比较的是各自的内存地址哪一个更大,返回值是整数1(true)或0(false)

函数:

    基础结构:

#include <stdio.h>

/* 基础结构*/

int main(void)
{
  int m = addNum(3); /* 使用函数, 3 传递的参数*/
  printf("m=%d", m);
}

int addNum(int a)   /* int addNum 定义函数名称  (int a 传递参数,可以传递多个,使用逗号分开) */
{                /*{函数体:使用此方法需要执行的语句}*/
  return a;      /* return  a; return  返回值,返回计算之后的数据 */
}


参数:

传递的参数是拷贝的数据,非自身数据,不会实时更新数据;
除非
 1 函数返回最新的数据,才可以获取并赋值最新的数据
 2 直接修改自身的署,传入变量的地址(函数不要返回内部变量的指针)

void Swap(int* x, int* y) {
  int temp;
  temp = *x;
  *x = *y;
  *y = temp;
}

int a = 1;
int b = 2;
Swap(&a, &b);

//参数为数组
int sum_array(int a[n], int n) {
  // ...
}

int sum = sum_array(a, 4);
//等同于
int sum = sum_array((int []){3, 5, 7, 3}, 4);

  /*多维数组*/
int sum_array(int n, int m, int a[n][m]);

  参数数量是不确定时,声明函数的时候,可以使用省略号...表示可变数量的参数

 注意:...符号必须放在参数序列的结尾,否则会报错

#include<stdio.h>  
 #include<stdarg.h> /* 可以操作可变参数的类*/
 double average(int i, ...);

double average(int i, ...) {
  double total = 0;
  va_list ap;            /*va_list 一个数据类型,用来定义一个可变参数对象*/
  /*  va_start(ap, i); 将参数i后面的参数统一放入ap*/
  va_start(ap, i);       /*va_start 一个函数,用来初始化可变参数对象  va_start(可变参数对象,正常参数,用来为可变参数定位)*/
  printf("i=%d \n", i);
  for (int j = 1; j <= i; ++j) {
    /* va_arg(ap, double)用来从ap依次取出一个参数,并且指定该参数为 double 类型*/
    total += va_arg(ap, double); /* va_arg 一个函数,用来取出当前那个可变参数 va_arg(可变参数对象,当前可变参数的类型)*/
    printf("total=%d \n", total);
  }
  printf("ap=%d \n", ap);
  va_end(ap);           /* va_end 一个函数,用来清理可变参数对象*/
  return total / i;
}

int main(void)
{
  int total=average(0,8,10);
  printf("total=%d", total);
}

递归函数:

函数可以调用自身,这就叫做递归(recursion)

例如:
#include <stdio.h>

int main(void)
{
  int m = addNum(3);
  printf("m=%d", m);
}

int addNum(int a)
{
  a++ ;
  if (a <= 5)
  {
    addNum(a);
  }
  else
  {
    return a;
  }
}

注意:在使用递归函数时,注意做好判断,不然容易出现死循环

入口函数:

main()是程序的入口函数,所有的程序必须要有
类似于页面加载方法,首先执行此方法
其他的方法需要通过main调用

#include <stdio.h>

int main(void)
{
  int m =3;
  printf("m=%d", m);
  return 0;     /* 表示函数结束运行,返回0;  返回非零数据表示运行失败*/
}

函数指针:

调用函数的方法
// 写法一  常用的 函数名本身就是指向函数代码的指针,通过函数名就能获取函数地址
print(10)

// 写法二
(*print)(10)

// 写法三
(&print)(10)

// 写法四
(*print_ptr)(10)

// 写法五
print_ptr(10)

 #include<stdio.h>  


 int addNum(int a)
{
  return a;
}

int main(void)  /*调用其他方法时,其他方法要么写在最前面, 或者使用函数原型*/
{
  // int m = addNum(3);
  // printf("m=%d", m);
  int m = (*addNum)(10);
  printf("m=%d", m);
}


优点:
可以明确参数是一个函数
  参数一就是一个函数
int compute(int (*myfunc)(int), int, int);
函数原型:提前告诉编译器,每个函数的返回类型和参数类型

int twice(int);

int main(int num) {
  return twice(num);
}

int twice(int num) {
  return 2 * num;
}

注意,函数原型必须以分号结尾。
      一般来说,每个源码文件的头部,都会给出当前脚本使用的所有函数的原型。

exit函数:

作用:终止程序的运行
前提: #include<stdlib.h>
返回值(stdlib.h已定义,无需定义):
   EXIT_SUCCESS(相当于 0)表示程序运行成功
   EXIT_FAILURE(相当于 1)表示程序异常中止
atexit()函数:
   用来登记exit()执行时额外执行的函数,用来做一些退出程序时的收尾工作

说明符:

 extern 说明符 :

extern int foo(int arg1, char arg2);
extern 表示:导入使用,声明此方法未在此文件定义,是从其他文件内引入使用的

int main(void) {
  int a = foo(2, 3);
  // ...
  return 0;
}

static 说明符:

1 用于函数内部声明变量时,表示该变量只需要初始化一次,不需要在每次调用时都进行初始化

static可以用来修饰函数本身,表示该函数只能在当前文件里使用,如果没有这个关键字,其他文件也可以使用这个函数(通过声明函数原型)

3 用在参数里面,修饰参数数组

int sum_array(int a[static 3], int n) { /* 数组长度至少为3*/
 
}

注意:

 static修饰的变量初始化时,只能赋值为常量,不能赋值为变量

   局部作用域中,static声明的变量有默认值0

const 说明符:

1 函数内部不得修改该参数变量

void f(const int* p) {
  int x = 13;
  p = &x; // p本身的地址是允许修改的
  *p = 0; //const声明的* p 不能修改,修改之后报错
}
void f(int* const p) { //不允许修改p,就使用const定义
  int x = 13;
  p = &x; // 该行报错
}


/* p和*p 都不允许修改 */
void f(const int* const p) {
}

内存管理:

  分类:

    系统管理:局部变量存放的内存区域(”栈“(stack)),运行结束后自动从内存卸载

   用户手动管理:全局变量存放的内存区域(”堆“(heap)),运行结束后不会自动卸载,需要用户手动释放,此现象叫”内存泄漏“(memory leak)

void 指针:

void:不定类型的指针
     可以指向任意类型的数据,但是不能解读数据
     void 指针与其他所有类型指针之间是互相转换关系
注意,由于不知道 void 指针指向什么类型的值,所以不能用*运算符取出它指向的值

内存操作:

   分配内存:

      malloc:
#include <stdio.h>
#include <stdlib.h> /*内存管理的操作*/


int main(){
  /* malloc(分配的内存字节数--非负整数)  任意类型的数据分配内存  返回值:无类型的 void 指针*/
    //malloc不会对所分配的内存进行初始化,需要自行初始化后再使用,想要初始化为0,还要额外调用memset()函数
   
  int* p = malloc(sizeof(int));
  *p = 12;
   if (p == NULL) {
  // 内存分配失败
 }
  printf("%d\n", *p); 
 

  int* n = (int*) malloc(sizeof(int)); //强制转换数据为整数类型
  printf("n=%d\n",*n);

  int* i = (int*) malloc(sizeof(*i));
  printf("i=%d\n",*i);

  //作用:
   //1 为数组和自定义数据结构分配内存
   int* t = (int*) malloc(sizeof(int) * 10);

    for (int i = 0; i < 10; i++)
      t[i] = i * 5;
    printf("t==%d",t);
  //2 创建动态数组
  int m;
  int* a = (int*) malloc(m * sizeof(int)); //可以根据变量m的不同,动态为数组分配不同的大小

}
calloc:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
  // calloc(数据类型的值的数量,数据类型的单位字节长度) 分配内存, 返回值:无类型的 void 指针,分配失败时,返回 NULL
    //calloc所分配的内存全部初始化为0
  int* p = calloc(10, sizeof(int));
  printf("p==%d, %d \n",p,*p);
  // 等同于
  int* n = malloc(sizeof(int) * 10);
  memset(n, 0, sizeof(int) * 10);
  printf("n==%d, %d \n",n,*n);
}

释放内存:

   free:
#include <stdio.h>
#include <stdlib.h>

int main(){
  //free(malloc返回的内存地址)  释放malloc函数分配的内存
    //分配的地址释放之后,不可操作和再次释放
    // 注意:函数调用成功后,一定要释放内存,否则会导致多次调用函数所生成的内存下次无法访问
  int* p = (int*) malloc(sizeof(int));
  *p = 12;
  printf("address=%d",p);
  free(p);
}

修改内存:

   realloc:
#include <stdio.h>
#include <stdlib.h>

int main(){
  //realloc(已经分配好的内存块指针,内存块的新大小,单位为字节) 修改已经分配的内存块的大小,可以放大也可以缩小  , 返回新的内存块的指针,分配失败返回null
    // 已经分配好的内存块指针--参数设置为null或者不写,会创建一个新的指针
    // 内存块的新大小  --------参数设置为0,会释放内存块
    //realloc不会对内存块进行初始化
  int* p = calloc(10, sizeof(int));
  float* new_p = realloc(p, sizeof(*p * 40));
  if (new_p == NULL) {
    printf("Error reallocing\n");
    return 1;
  }else{
    printf("win--- \n");
  }
}

拷贝内存:

     memcpy拷贝:
#include <stdio.h>
#include <string.h>

//自定义复制内存函数:
void* my_memcpy(void* dest, void* src, int byte_count) {
  char* s = src;
  char* d = dest;

  while (byte_count--) {
    *d++ = *s++;
  }

  return dest;

}

int main(){
   //memcpy(拷贝地址,被拷贝地址,拷贝的字节数)   前两个参数:不限制指针类型,各种类型的内存数据都可以拷贝
  char s[] = "Goats!";
  char t[100];

  // memcpy(t, s, sizeof(s));  // 拷贝7个字节,包括终止符
  int m = my_memcpy(t, s, sizeof(s));

  printf("m=%s\n", m);  // "Goats!"

  return 0;
}
memmove复制:
#include <stdio.h>
#include <string.h>

int main(){
  //memmove(拷贝地址,被拷贝地址,移动的字节数)   允许目标区域与源区域有重叠。如果发生重叠,源区域的内容会被更改
  char x[] = "Home Sweet Home";
  printf("x=%s\n", (char *) memmove(x, &x[5], 10));
}

比较内存:

  memcmp:
#include <stdio.h>
#include <string.h>

int main(){
  //memcmp(比对者1,比对者2,比较的字节数)  返回值:整数 
                                        //相同时返回0,参数一大于参数二时返回大于0,参数一小于参数二返回小于0

  // char* s1 = "abc";
  // char* s2 = "acd";
  // int r = memcmp(s1, s2, 3); // 小于 0
  //  printf("memcmp result=%d",r);

  //比较内部带有字符串终止符\0的内存区域
  char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'};
  char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'};
  
  printf ("3=%d \n",memcmp(s1, s2, 3) == 0) ;// true
  printf ("4=%d \n",memcmp(s1, s2, 4) == 0) ;// true
  printf ("7=%d \n",memcmp(s1, s2, 7) == 0) ;// false

}

restrict 说明符 :

    restrict :内存区域只有当前指针一种访问方式,其他指针不能读写该内存,这种指针称为“受限指针”(restrict pointer)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序媛刘刘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值