c入门(中)

c/c++语法入门

位运算

运算符

&与,|或,^异或,~取反
eg

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

移位运算<<左移,>>右移

int bit=2;//bit=2
bit<<=1;//bit=4
bit>>=3;//bit=1

参考C 运算符

枚举

.h

//定义枚举
enum day
{
Mon=1,tue,wed,tur,fir,hs,sun
};

.c

#include <stdio.h>
#include "test.h"

int main()
{
    enum day test;//创建枚举变量
    test = Mon;//设定枚举值
    switch (test)//快速判断过程
    {
    case Mon:
        printf("周一");
        break;
    case tue:
        printf("周二");
    default:
        break;
    }
}

便于统一数值格式用于判断

指针

二维数组和指针

int main()
{
    int var_runoob = 10;
    char *t[] = {"hello", "3", "ttthsjfi","test","test"}; //定义指针数组,相当于数组中的每个值都是指针,指向各个字符串首位,使用与List<String>类似,取值方式t[i]
    char **p = t;                           //二维指针,将指针数组的位置传递给二维指针,
    printf("p=%d,t=%d\r\n", &p, &t);            //输出的地址是字符串数组的位置,pt两值相同
    printf("%s\r\n", t[0]); 
    printf("%s\r\n", p[0]);
    return 0;
}

需要注意的是,可以通过sizeof获取t的大小(sizeof(t)=8*num,但无法获取p的大小,(sizeof(p)恒为8,将数组以二维指针的方式传递出去时要传递数组大小才行。

void指针(void *)

通过sizeof,可以了解不同指针所占用的内存

int * i1;
double *d1;
void * v;
char * c1;
uint *ui1;
short *s;

printf("the size of *int is %d\r\n",sizeof(i1));
printf("the size of *double is %d\r\n",sizeof(d1));
printf("the size of *void is %d\r\n",sizeof(v));
printf("the size of *char is %d\r\n",sizeof(c1));
printf("the size of *short is %d\r\n",sizeof(s));
printf("the size of *uint is %d\r\n",sizeof(ui1));

结果
the size of *int is 8
the size of *double is 8
the size of *void is 8
the size of *char is 8
the size of *short is 8
the size of *uint is 8

这是在gcc编译下的结果,可以理解为该标准下的指针所占用的内存都是8字节
使用void *的方式类似于c#中var的使用,只不过引用的时候要表明当前的类型,不然就会报出不允许使用不完整的类型

int i=2147483647;               
double d=1.1;
int * i1;i1=&i;
double *d1=&d;
void * v;
v=i1;
printf("the int is %d,the void is %d\r\n",*i1,*(int *)v);
v=d1;
printf("the double is %f,the void is %f\r\n",*d1,*(double *)v);

结果
the int is 2147483647,the void is 2147483647
the double is 1.100000,the void is 1.100000

引用void *指针前要进行强制类型转换(type ),void 常作为函数参数使用,比如在内存操作函数

void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );

由于不知道传入的指针类型,直接使用void * 就规避了该问题

参考
C 语言中 void* 详解及应用

函数指针

将函数当作指针传递

//函数原型,打印出字符串数组的所有字符串
void printf_str(char **p,int str_len)
{
    int i=0;
    for(;i<str_len;i++){
        printf("%s     ",p[i]);
    }
    printf("\r\n");
}


int main(){
    void (*f) (char **,int);//按照函数定义函数指针
    f=printf_str;//获取函数的地址
    f(p,sizeof(t)/8);//相当于可以直接使用该函数
}
回调函数用法

由此,可以将函数当作指针进行传递,在跨文件时可以使用该方法传递函数或者在单向权限的地方作为回调函数,让没有权限的地方能够通过你提供的函数指针进行工作,这其实类似于接口。A公司提供接口规范z,B公司以该规范z实现函数k,B公司的设备接入A公司后,A公司程序内部在实现过程中需要B公司设备工作时,只要在自己的代码中,引入B公司按照规范实现的函数k(一般使用dll引入),代入参数即可运行,不必了解B公司实现方法是什么。例子

//A公司提供的接口规范z
void printf_str(char **p, int str_len);

//k函数原型(B公司实现的功能)
void printf_str(char **p, int str_len)
{
    int i = 0;
    for (; i < str_len; i++)
    {
        printf("%s     ", p[i]);
    }
    printf("\r\n");
}

//A公司调用过程
//参数p相当于回调函数
void sendfunc(char **str, int len, void (*p)(char **, int))
{
    void (*f)(char **, int); //按照函数定义函数指针
    f = p;                   //获取函数的地址,可以直接使用回调函数k
    f(str, len);
    change_str(str, len); //首尾交换
    f(str, len);
}

sendfunc(p, sizeof(t) / 8, printf_str);//执行函数,使用了B公司提供的printf_str方法

这在面向对象语言中更加简洁。

指针学习过程中可直接运行的代码如下

#include <stdio.h>

void change_str(char *p[], int);
void printf_str(char **p, int str_len);
void sendfunc(char **str, int len, void (*p)(char **, int));
int main()
{
    int var_runoob = 10;
    char *t[] = {"hello", "3", "ttthsjfi", "test", "test2", "t"}; //定义指针数组,相当于数组中的每个值都是指针,指向各个字符串首位,使用与List<String>类似,取值方式t[i]
    char **p = t;                                                 //二维指针,将指针数组的位置传递给二维指针,
    printf("p=%d,t=%d\r\n", &p, &t);                              //输出的地址是字符串数组的位置,pt两值相同
    printf("%s\r\n", t[0]);                                       //指针数组取值
    printf("%s\r\n", p[0]);

    sendfunc(p, sizeof(t) / 8, printf_str);

    return 0;
}

//字符串数组首尾交换
void change_str(char **p, int str_num)
{

    if (str_num % 2 == 0) //奇偶判断
    {
        int i = 0;
        for (; i < (str_num / 2) - 1; i++)
        {
            char *str1 = NULL;
            str1 = p[i];               //保存字符串的地址
            p[i] = p[str_num - 1 - i]; //尾首交换
            if (str1 != 0)             //非空判断
                p[str_num - 1 - i] = str1;
        }
    }
    else
    {
        int i = 0;
        for (; i < (str_num / 2); i++) //位于中间的不替换
        {
            char *str1 = NULL;
            str1 = p[i];               //保存字符串的地址
            p[i] = p[str_num - 1 - i]; //尾首交换
            if (str1 != 0)             //非空判断
                p[str_num - 1 - i] = str1;
        }
    }
}

//示例函数,p参数相当于回调函数
void sendfunc(char **str, int len, void (*p)(char **, int))
{
    void (*f)(char **, int); //按照函数定义函数指针
    f = p;                   //获取函数的地址,可以直接使用回调函数
    f(str, len);
    change_str(str, len); //首尾交换
    f(str, len);
}

//函数原型
void printf_str(char **p, int str_len)
{
    int i = 0;
    for (; i < str_len; i++)
    {
        printf("%s     ", p[i]);
    }
    printf("\r\n");
}

结构体

常用结构体

定义

struct Books//结构体
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;

};

使用类似于对象,struct内成员可以是各种类型,实例化过程可以有没有定义的内部类型,如上的book_id可空。
示例

    struct Books b1; //结构体中的字符串类型不能直接赋值添加或修改,要使用strcpy函数去修改
    strcpy(b1.title, "C Programming");
    strcpy(b1.author, "Nuha Ali");
    strcpy(b1.subject, "C Programming Tutorial");
    b1.book_id = 122; //数字可以直接修改
    strcpy(b1.author, "hhh");

可以使用指针传递实例化的结构体,并进行内部成员修改

    struct Books *b;            //定义一个同类型的结构体变量
    b = &b1;                    //直接获取结构题对象地址
    strcpy(b->title, "change"); //使用->来修改,字符串修改规则同上
    b->book_id = 111;

传递参数修改

change_msg(b);//传递指针修改
void change_msg(struct Books *b)
{
    b->book_id=13;
    strcpy(b->author,"chen");
    strcpy(b->subject,"math");
    strcpy(b->title,"death");
}

位域

定义

struct field
{
    int a:1;//分号后面表示占用2进制几位
    int b:8;
    int c:2;
};

示例

    struct field f1;
    f1.a=1;//位域以二进制定义符号,该位只提提供一位,所以没有溢出
    f1.b=255;//该位提供8位,即0-255,此处导致溢出
    f1.c=44;//该位提供2位,即0-3,此处溢出
    //虽然溢出,但不影响编译,会报溢出的警告

预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。意即,预处理可以将特殊文本进行处理后再交由编译器编译。
最常用的预处理是宏定义

#define ok 1

可以使用判断语句实现更多

#define ok 1
#ifdef ok
//do something
#endif

对于系统的宏(包括当前时间,代码文件位置,行数等的)定义如下

__DATE__	当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
__TIME__	当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
__FILE__	这会包含当前文件名,一个字符串常量。
__LINE__	这会包含当前行号,一个十进制常量。
__STDC__	当编译器以 ANSI 标准编译时,则定义为 1。

这类宏定义可以直接用在代码中

printf("this time is %s",__TIME__);

this time is 13:01:51

预处理器运算符

字符串常量化运算符(#)&& 标记粘贴运算符(##)(仅能在宏定义中使用)
#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void)
{
   int token34 = 40;
   
   tokenpaster(34);
   return 0;
}

输出
token34 = 40

找不到比这个更好的例子了,出处C 预处理器
可以理解为,#可以将参数字符串化,##可以直接连接两个参数并表现为参数整体。

参数化的宏

相当于快捷定义一个函数

#define hello(x,y) ((x)*(y))
printf("%d",hello(4,5));        //20

typeof & define

define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

示例

#define ok 1

typedef char uchar;

int main()
{
    uchar *s;
    s = "hello";
    printf(s);
    if (ok)
    {
        printf("ok");
    }else{
        printf("false");
    }
    return 0;
}

输出:hellook

扩展
对于typedef,用法可以视为将基础类型改名,在指针函数上,可以这样用

typedef void   (*test_t)(int);

这相当于直接定义一个指针函数

int main(){
    void (*test_t) (int);
}

使用方法也相同

//函数原型
void test(int i){
    printf("%d",i);
}


test_t t1;
t1=test;
t1(i);

属性

在定义函数时可以使用属性字段修饰该函数,这类似于c#中的注解属性。
eg
a.h

void test()__attribute__((weak));

a.c

#include<stdio.h>
#include"a.h"
#include"b.h"
void test(){
   printf("main file");
}
int main()
{
   test();
   return 0;
}

b.h

void test();

b.c

#include<stdio.h>
#include"b.h"
void test(){
    printf("this is test2");
}

当运行a.c中的main时,输出为this is test2,原因就是a.h中为a.c本地实现的test()函数添加了weak属性,使其优先级低于外部引入文件中的test();

错误处理

系统中有一个全局变量errno,在异常时会被赋值,可以通过该值判断是否出现异常及异常类型

#include <errno.h>

extern int errno;//声明errno全局变量

int main(void)
{
FILE * pf;
   int errnum;
   pf = fopen ("test", "rb");
   if (pf == NULL)
   {
      errnum = errno;
      fprintf(stderr, "错误号: %d\n", errno);                  //输出异常代码
      perror("通过 perror 输出错误");                           //可以输出异常原因,函数参数为异常原因前个人定制的输出格式
      fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));//通过strerror函数返回异常原因并输出
   }
   else
   {
      fclose (pf);
   }
   return 0;
}

结果
错误号: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory

尝试以异常判断的方式实现判断二维字符串数组内字符串数量,结果无法实现

内存管理

常用函数

  1. void calloc(int num, int size);
    在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num
    size 个字节长度的内存空间,并且每个字节的值都是0。
  2. void *malloc(int num);
    在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
  3. void free(void *address);
    该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
  4. void *realloc(void *address, int newsize);

由上可知,calloc和malloc都可用于分配内存,区别在于前者初始化为0,后者不初始化

示例

原例C 内存管理

一:内存分配
#include <stdlib.h>
#include <string.h>

int main(void)
{
   char name[100];
   char *description;
 
   strcpy(name, "Zara Ali");
 
   /* 动态分配内存 */
   description = (char *)malloc( 200 * sizeof(char) );
   if( description == NULL )//当储存分配失败,输出异常
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcpy( description, "Zara ali a DPS student in class 10th");//储存分配成功则储存新数据
   }
   printf("Name = %s\n", name );
   printf("Description: %s\n", description );
   free(description);//释放内存
}

当创建的区域不够储存时,会报corrupted top size的异常

二:内存扩大缩小或释放
int main()
{
   char name[100];
   char *description;
 
   strcpy(name, "Zara Ali");
 
   /* 动态分配内存 */
   description = (char *)malloc( 30 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcpy( description, "Zara ali a DPS student.");
   }
   /* 
   假设您想要存储更大的描述信息,
    */
   description = (char *) realloc( description, 1 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcat( description, "She is in class 10th");
   }
   
   printf("Name = %s\n", name );
   printf("Description: %s\n", description );
 
   /* 使用 free() 函数释放内存 */
   free(description);
}

对于realloc,参数二指扩展的目标大小,可相对原来的小,当大小比原本开辟的内存空间小时,从尾部开始清除数据。
参考
C—动态内存分配之malloc与realloc的区别

其他

goto跳转

for(item=0;item<16;item++){
      if(item>10)
      goto p_test;
   }
p_test:
printf("now the item is %d\r\n",item);

result
now the item is 11
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值