翁恺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; } }
结构的使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//结构 ,若声明在函数内部,则只能在当前函数使用 //声明于函数外部,标识可以被多个函数调用 //结构形式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这个定义结束