翁恺C语言基础学习(基本语法结构概念)

类型转换

对于printf函数,任何比int类型小的数都会转换成int,而scanf()则不会,需要手动设置读取的数据类型 输入整数   scanf("%d",&a) 输入short类型scanf("%hd",&a) 

逻辑运算符以及优先级

 

 "!age<20" 解读 就是  age 小于20 再取反  

int age =19;
if(!age<20){
    printf("age=19 >20 "); //正确输出
}else{
    printf("age=19 <20");
}

 

 

 

 

逗号运算符 ,主要用于for循环中的条件多次运算

 

    int b = (3 + 4, 5 + 6);
    printf("b = %d\n", b);  //输出 b = 11
    int i, j;
    for (i = 0, j = 10; i < 1; i++) {
        printf("i = %d\n", i);        //输出 i = 0
        printf("j = %d\n", j);        //输出 i = 10
    
    }

函数: 单一出口原则

函数先后顺序:C编译器自上而下的解析代码!因此在调用函数前,需要在方法前定义或者声明函数    

本地变量:函数每次运行都会产生独立的变量空间,在这个空间中是函数这次运行所独有的称作“本地变量”,函数参数也属于本地变量

C语言函数内部不能再次定义函数

函数原型的定义方式

void test1(void); //明确标识此函数不接收任何参数 
void test2();    //参数未知,能接收任意函数,可能导致未知错误
void sum(int ,ini); //规范标识,明确标识该函数接收2个int类型的参数,参数名称可加可不加

 

sum(a,b);        //逗号在函数调用的时候作为参数分割符 
sum((a,b));        //加上了括号,那么逗号则是运算符,代表着只传递了一个参数给sum函数
//正例  函数只会再末尾处返回正确的值 
int max(int a,int b)
{
    int maxValue ;
    if(a>b)
    {
        maxValue = a;
    }else{
        maxValue = b;
    }
    return maxValue;
}
//反例  if和else 都可能会是函返回结果 
int max(int a,int b)
{
    if(a>b)
    {
        return a;
    }else{
        return b;
    } 
}

 

 

数组

C99之前,数组元素数量必须是确定的字面量,C99之后允许动态分配数组大小

一旦创建,不能改变其大小;数组在其内部依次排列,

数组作为参数时,函数内部不能用sizeof计算数组的长度

取址运算符“&”

获取变量的地址,它的操作数必须是变量,

地址值的长度取决于编译器,32位编译器为4个字节,64位则为8个字节

//数组与取址运算符
void
testArray(){ int a[10]; printf("%p\n",&a); //输出 000000000062FDF0 printf("%p\n",a); //输出 000000000062FDF0 printf("%p\n",&a[0]); //输出 000000000062FDF0 printf("%p\n",&a[1]); //输出 000000000062FDF4 }

 

 

指针变量——标识一个变量的内存地址

void p1()
{
    int a=7;            //定义普通变量 a =7 
    int *p = &a;        //定义指针变量 *p = 变量a的地址 
    *p=10;                //将指针变量地址上的值  修改为10; 
    printf("a=%d\n",a);    //输出 = 10 
 } 

 

指针应用一:交换两个变量的值

指针应用二:当函数运算时会发生错误,使用函数返回值判断函数是否正确运行,而运行结果采用指针返回

//指针应用、获取函数执行状态, 
int divide(int a,int b,int *result) {
    int ret = 1;            //定义函数状态值 
    if(b == 0 ) ret = 0;       //除数为0时,标识函数运行出错,无法继续运行 
    else{
        *result = a/b;        //满足条件,执行运算,将结果存入指针中 
    }
    return ret;                //返回函数的运行状态  
}

 

指针 与 const 

const修饰数组时,标识该数组内部的值为const 不能被修改

void testPointConst(){
    //判断哪个被const的标识是 const在 *前面 还是在 *后面   ,
    //只要*在const前面,那么就是该指针不可修改,否则 指针指向的值 不可通过该指针去修改 
    int i = 10;
    int b = 20; 
    printf("i =%X \n",&i);        
    printf("b =%X \n",&b);         
    const int* p = &i;        //标识这个i不能通过指针去修改,但指针本身可以进行运算 ,i也可以进行运算 
    int const * p0 = &i;     //含义同上,写法区别 
    int const *p1 = &i;     //含义同上,写法区别
    int const* p2 = &i;      //含义同上,写法区别
//------------分割线----------------------------
    int *const p3 = &i;
    int * const p4 = &i; 
    p0--; 
    printf("*p0 =%d \n",*p0);     //输出 20 
    
    //*p = i++;  //此处错误 assignment of read-only location '*p'
    //p3++;  //此处错误  increment of read-only variable 'p3'
    //p4++;   //此处错误  increment of read-only variable 'p4'
    i++; 
    printf("*p =%d \n",*p);     //输出 11 
    
    *p3 = 11; 
    printf("p3 =%X \n",p3); 
    printf("p3-- =%X \n",p3);
    printf("*p3 =%d \n",*p3);
    
} 

 

指针的加法运算

//指针数组应用 
void testPointArray(){
    int a[10];
    a[0] = 1;
    a[1] = 2;
    int *p = &a[0] ;
    printf("a[0] %X\n",&a[0]);    // 0x62fdf0
    printf("a[1] %X\n",&a[1]);  // 0x62fdf4
    printf("p = %X\n",p);          //0x62fdf0
    p = p + 4;                    //指针做加法运算时,等于  (指针 加上 这个数 乘以指针类型的长度 ) 
    printf("p+4 = %X\n",p);     //0x62fe00
    *p = 10;
    printf("a[0] = %X\n",a[0]);    // 1
    printf("a[1] = %d\n",a[1]);    //2
    printf("a[4] = %d\n",a[4]);    //10
} 

 

强制类型转换

//强制类型转换 
void testPoint() {
    int a = 65536;                 //FFFF  int类型为 4字节  二进制为   1000,0000,0000,000,,1111,1111,1111,1111 
    int* p = &a;
    short* p1 = (short*)p;        //强转为short后  只读取 16位数据   根据补码规则,所有*p1的值=0 
    printf("%d\n", a);            //输出 65536; 
    printf("%d\n", *p1);             //  输出0; 
}

 

 

动态分配内存

//动态内存分配
void testMalloc() {
    //malloc 函数在 <stdlib.h>中定义 ,返回值为void*类型   
    //c99之前 动态内存分配 
    int number;
    int* a;
    int i;
    printf("输入数组大小\n");
    scanf("%d", &number);        //读取一个整数 
    a = (int*)malloc(number * sizeof(int));          //向os请求分配 int类型 * number块连续的内存空间 ,该函数返回值为void* 因此强转成需要的int类型 
    a[1] = 2;    //指针<->数组  存在转换性 
    a++;
    int c = *a;
    printf("c = %d", c);  //输出2 
    free(a);             //释放之前 申请的内存空间 
}

 

字符串

以0(整数)结尾的一串字符,0是标识字符串的结束,但它不是字符串的一部分,计算字符串长度的时候不包含这个0,字符串以数组的方式存在,以数组或指针的方式访问(常用指针),string.h中定义了很多处理字符串的函数

字面量 “hello”会被编译器解析成一个数组放在某处,并且在数组末尾处添加‘\0’标识字符串结束,因此它的长度等于6; 可以用来初始化字符数组

两个相连的字符串编译器会将他们联系在一起

 

void testString() {
    char b = 'a';
     char*  s = "hello,world";     //指针s 初始化为指向一个字面量,历史原因编译器接收不带sonst的写法 但实际解析时 的s 是  const char* s 
    //s[0] = 'b';                    //因此不可以修改该字符数组 

    printf("%p\n", b);
    printf("%p\n", s);
    //字符串使用原则:
    //char s[] = "hello,world";                若使用数组,那么这个字符串作为本地变量空间自动被回收
    // char*  s = "hello,world";            若使用指针,不知道字符串在哪里,通常用于处理参数,动态分配空间时使用
    // 字符串可以用 char*的方式表达,但char* 不是绝对的字符串,它的本意是指向字符的数组,
    //只有当它所指的字符结尾处带有结尾的'0'时,才能被确认为是字符串

    //字符串输入输出 格式化   scanf("%s",str);  printf("%s" ,str);
    //scanf()函数非安全,因为它不确定读入的字符串长度有多少;
    //安全使用: 在%和s之间 插入一个 确定读取的长度 如 scanf("%7s",str),标识这一次只读取7个字符,多余的字符将会被下次scanf时获取到


    char buffer[100] = ""; // 这是一个空的字符串,buffer[0] = ‘\0’;
    char buffer1[] = "";    //相当于用""初始化了该字符串,它的长度只有1

    //字符串数组
    char a1[][10] = { "hello" };        //二维数组    ,标识数组中每一个元素的长度都是为 char[10]
    char* a[10];                        //字符串数组,标识这个数组中有10个指针,指向不确定的位置
    a[1] = "hello world\0";
    a[2] = "hello c\0";

    printf("%s\n", a[1]);
    printf("%s\n", a[2]);

    //getchar(int a) 获取一个字符   ,返回获取的个数
    //putchar(int a)    输出一个字符,
}

 

字符串函数

 

//字符串处理函数
void testStrFun() {
    //string.h  C语言标准库携带的头文件
    char str[] = "hello1";
    char str1[] = "hello";
    printf("str长度 = %lu \n", strlen(str));                //输出5
    printf("str长度 = %lu \n", myLength(str));                //输出5
    printf("str长度 sizeof= %lu \n", sizeof(str));        //输出6 因为字符串末尾还有一个'\0'

    printf("str的地址是=%p\n", str);
    printf("str1的地址是=%p\n", str1);
    printf("mycmp是否相等%d\n", mycmp(str, str1));            //返回0 标识2个字符串相等
    printf("是否相等%d\n", strcmp(str, str1));                //返回0 标识2个字符串相等

    //拷贝函数 strcpy(char *restrict dst,const char *restrict src) //将src的字符串拷贝到dst中 返回dst   restrict标识src与dst不重叠(C99)
    char* dst = (char*)malloc(strlen(str) + 1);
    char* res = strcpy(dst, str);
    printf("res的地址是=%p\n", res);
    printf("dst的地址是=%p\n", dst);
    printf("%s \n", res);
    printf("dst = %s\n", dst);
    char* myDst = (char*)malloc(strlen(str) + 1);
    printf("myStrCpy = %s\n", myStrCpy(myDst, str));

    //字符串拼接   cahr * strcat(char* restrict s1,const char *restrict s2) 
    //将字符串s2拷贝到s1的后面,拼接成一个长的字符串,返回s1  条件s1必须具有足够的空间
    char* s2 = "world";
    char s1[20] = "hello,";
    strcat(s1, s2);
    printf("strcat = %s\n", s1);    //输出 hello,world

    // strcpy,strcat,strcmp,都是非安全函数,c语言标准库中也提供了其安全版本
    //char * strncpy(char *restrict dst,const char *restrict src,size_t n)    // n 标识最多拷贝n个字符
    //char * strncat(char *restrict s1,const char *restrict s2,size_t n)    // n 标识最多拼接n个字符
    //char * strncmp(const char *s1,const char *restrict src,size_t n)        // n 标识 比较指定个数的字符,例,n=3,只比较前三个

    //字符串查找函数  
    //char * strchr(const char *s,int c);    //从左边开始查找指定字符     返回NULL 标识未找到
    //char * strrchr(const char *s,int c);    //从右边开始
     char s3[] = "helloworld";
    char *ps3 = strchr(s3, 'l');
    //小技巧 查找第二个
    //ps3 = strchr(ps3 + 1, 'l'); 
    //复制指定位置后的字符串
    char* t = (char*)malloc(strlen(ps3) + 1);
    strcpy(t, ps3);
    printf("t = %s\n", t);    //输出lloworld
    free(t);
    printf("*ps3 = %c\n", *ps3);
    //复制指定字符前的字符串
    char s4[] = "hello";
    char* p1 = strchr(s4, 'l');
    char temp = *p1;
    *p1 = '\0';
    char *t1 = (char*)malloc(strlen(s4) + 1);
    strcpy(t1, s4);
    printf("t1=%s\n",t1);
    *p1 = temp;
    printf("s4=%s\n", s4);
    free(t1);

    //strstr(const char* s1,const char* s2)   //查找指定的字符串,返回找到的字符串指针
    //strcasestr(const char* s1,const char* s2)   //忽略大小写 查找指定的字符串,返回找到的字符串指针
    char* find = strstr(s4, "ll");
    printf("find = %s\n", find);


}

 

枚举:是一种用户自定义数据类型, enum 类型名称 {属性名称1,属性名称2};  它的类型为int,依次从零开始

//枚举
enum Color{    red,yellow,green,blue,colorLength};
void testenum() {
    enum Color deviceColor = 2;
    switch (deviceColor)
    {
    case red:printf("红色\n");break;
    case yellow:printf("黄色\n");break;
    case green:printf("绿色\n");break;
    case blue:printf("蓝色\n");break;
    }
}

 

 

结构的使用

//结构   ,若声明在函数内部,则只能在当前函数使用
//声明于函数外部,标识可以被多个函数调用
//结构形式1
struct  date
{
    int year;
    int month;
    int day;
};
//结构形式二:隐藏结构名称,结构末尾处声明p1,p2两个结构变量
struct {
    int x;
    int y;
} p1,p2;

//结构形式三:声明结构,结构末尾处声明p1,p2两个结构变量
struct point {
    int x;
    int y;
} p3, p4;

void test_struct() {

    struct date today;
    today.year = 2021;
    today.month = 7;
    today.day = 2;
    printf("today is %i-%i-%i \n", today.year, today.month, today.day); //输出2021-7-2
    struct date thisday = { .month=7,.day=2 };
    printf("thisday is %i-%i-%i\n ", thisday.year, thisday.month, thisday.day);    //输出0-7-2
    //当只初始化结构中部分属性的值时,其他属性被赋值为0
    struct person {
        int age;
        char* name;
    };
    struct person zhangsan = { .age = 16 };
    struct person lisi = zhangsan;
    lisi.name = "李四";
    //结构变量的名字不是结构变量的地址,获取结构变量的地址,必须使用‘&’运算符
    printf("person zhangsan is %s,年龄是%i\n ", zhangsan.name, zhangsan.age);    //输出  person is (null),年龄是16
    printf("person lisi is %s,年龄是%i\n ", lisi.name, lisi.age);    //输出  person is 李四,年龄是16

    //结构作为函数变量时,函数内部获取的时该结构的值,也就是外部结构的克隆体,函数内部操作不会改变外部结构变量
    struct person *p = &zhangsan;
    printf("p->.age = %d\n", p->age);        //输出p->.age = 16;    ‘->’ 箭头符合标识该指针指向结构的属性值
}
//嵌套结构,地址空间分布
void test_nest_struct() {
    struct  rectangle
    {
        struct point start;
        struct point end;
    }rt1,rt2;
    printf("rt1的地址%p\n", &rt1);                        //012FF934
    printf("rt1中start 地址%p\n", &rt1.start);            //012FF934
    printf("rt1中end 地址%p\n", &rt1.end);                //012FF93C
    printf("rt1中start 中x的地址%p\n", &rt1.start.x);        //012FF934
    printf("rt1中start 中y的地址%p\n", &rt1.start.y);        //012FF938
}

//自定义别名
typedef long int64_t;        //int64_t 可以在其他地方代替 int使用
typedef struct { int age; char *name; } Person;        //标识 一个匿名结构,它的引用名称为Person
void test_typedef() {
    Person s = { 25,"张三" };
    printf("person is name = %s,age=%d\n", s.name, s.age);
}
结构的相关使用

 

 

联合体

//联合体(共用体) union
//结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员
typedef union MyUnion{int age;char ch[sizeof(int)];} People;
void testunion() {
    People people;
    people.age = 98;
    int i;
    for (i = 0; i < sizeof(int); i++) {
        printf("%02hhX", people.ch[i]);
    }
    printf("%d", people.age);
}

 

全局变量:任何函数内部都可以使用,在main函数之前被初始化,若没有初始化则编译器会将其赋值0(指针为null),初始化必须是一个确定值(常量or字面量),若函数内部存在同名变量,则该全局变量在函数内部被隐藏

静态本地变量:实际是全局变量,但是它的的作用域只在被定义的函数内部,“全局生命周期,本地作用域”

//尽量避免使用全局变量和本地静态变量
int *f1() {
    int i = 12;
    printf("i=%p\n", &i);    //输出    
    return &i;
}
int g() {
    int k = 22;

    printf("k=%p\n", &k);    //输出    k=005FF658
    printf("k=%d\n", k);    
}
void test_variable() 
{
    int *p = f1();
    printf("*p=%d\n", *p);    //输出f1函数返回值 12   地址值 i=005FF658
    g();                    //输出 k = 22,          地址值 i=005FF658
    printf("*p=%d\n", *p);    //输出 *p=-858993460  
    //说明*p指向的f1函数的本地变量i的内存空间在函数执行完毕后,又被分配到其他函数中使用!
}

宏:

/*
编译预处理指令,C语言中以“#”开头的的都是编译预处理指令
#define 用来定义一个宏 格式 : #define  <宏名称>  <值>  <//注释内容>
示例:#define PI 3.1415926
在C语言编译器编译之前,编译预处理程序会将程序中使用的宏,替换成原有的值,纯文本替换
tips: gcc--save-temps 可以查看相关文件

如果一个宏中有其他宏的名字,也会被替换
若宏超过一行,则需要在换行处加‘\’  
宏后面出现的注释不会被当作宏的内容

没有值的宏,用于条件编译,后面有其他的编译预处理指令来检查这个宏是否被定义过了
#define _DEBUG   
C语言预定义宏        '__LINE__','__FILE__','__DATE__','__TIME__','__STDC__'

带参数的宏,可以理解为一个表达式,最终时需要表达式的值,因此用一对括号将其包裹 ,参数也可能时表达式——也需要括号
正例    #define RADTODEG(X) ((X)*57.29578)

反例1:#define RADTODEG1(X) (X *57.29578)    
反例2:#define RADTODEG2(X) (X)*57.29578   

当把 x=(5+2)以表达式的方式传入反例1时  会得到: 5 + 2 *57.29578   
当执行  180/RADTODEG2(1)  以表达式的方式传入反例2时  会得到: 180/(1)  *57.29578 =10313.240400

参数宏与函数的区别:宏没有类型检查,效率高,关键词纯文本替换,因此空间占用高,函数有类型检查,以及调度等额外开销
部分宏会被inline函数取代

*/

void test_pretreatment() {

    //file 文件全路径,line 所在的行号
    printf("%s:%d\n", __FILE__, __LINE__);//d:\visualstudiowork\work_c\study_c\study_c\study_c.c:389
    printf("%s:%s\n", __DATE__, __TIME__);//Jul  2 2021:18:03:06  date 当前日期,time 当前时间
}

 

C语言项目组织结构

  #include <xxx.h>  :标识让编译器在系统指定目录下查找头文件
  #include “xxx.h”  :标识让编译器在当前目录下查找头文件,若未找到再去系统指定目录下查找

环境变量和编译器命令行参数也可以指定头文件查找的目录

编译器知道自己头文件的具体目录

#include  :  编译器预处理指令,它会将找到头文件内容,以纯文本的方式插入到当前的文件中,

 .h头文件,就是将函数原型放在这个文件中,在函数具体实现的“.c文件”include这个头文件,让编译器在编译的时候能正确识别函数的返回值,参数等限定内容,头文件在引用文件和实现文件之间形成一个沟通的桥梁,保证函数的一致性

static 修饰函数,变量时标识这个函数,变量不对外部公开,仅在当前的".c"文件中使用

 

 

 

 

 

标准头文件结构

#pragma once    //该指令可以保证该头文件只会被 引入一次,但部分编译器不支持

#ifdef ——A_HEAD——    //判断是否已经引入了这个头文件,并且定义为 ——A_HEAD——
#define ——A_HEAD——     //若未引用,则引用这个头文件
//变量声明 标识该变量能被外部所访问
extern double PI;
//函数原型声明
int max(int a, int b);


#endif // DEBUG            //标识 A_HEAD这个定义结束

 

 

posted @ 2021-07-05 09:14  迷~途  阅读(283)  评论(0编辑  收藏  举报