C基础

基础

1、变量类型

char          字符——%c

short         短整——%d

int           整型——%d

long          长整型——%d

long long     更长的整型——%d

float         单精度浮点——%f

double        双精度浮点——%lf

使用

//声明变量,可以引用其他工程内文件的全局变量
extern int val;//需要名字、类型和其他文件的相同
int a = 0; //全局变量,范围是整个工程
int main(){
    int b = 0;//局部变量,与全局变量名字冲突时,局部优先
    类型 变量名 = 值;//创建变量时最好赋一个值
    printf("%d", sizeof(变量名) );
    sizeof()//返回的是变量长度,单位是字节byte=8bit,比如char=1,shor=2,int=4;
    
}

常量

//define定义标识符常量,一般用全大写
#define MAX 100
//3、枚举常量
enum Sex
{
    MALE = 3,//给枚举值,赋初值后,打印MALE就会输出3,后面的元素依次递增
    FEMALE
};
int main(){
    //1、常量是不可变的
    const int num = 10;
    //2、创建数组时,长度必须是常量来指定,const修饰的是常变量,本质是变量,不等于常量
    int arr[纯数字] = {0};
    //3、枚举常量使用
    enum Sex s = MALE;//规定使用Sex类型的枚举时,只能使用定义的几种值
    printf("%d",MALE);//打印结果是0,指的是枚举值第一个
    
}

字符串

注意项:字符串结束标识是 \0,不会展示在页面上。

char arr[] = "abc";//字符串即字符数组,\0算作一个位置,“abc”实际有4个元素
char arr1[] = {'a', 'b', 'c'};//这种方法创建时,后面不会有\0这个元素,会导致打印出其它乱码字符
strlen(arr);//这个函数返回的结果不包括\0

%s 是字符串占位符
%c 是字符占位符

转义字符

printf("\t");// \+字母代表一个转义字符,打印出来\t是tab制表符
"\n"    换行
"\\"    \
//更多转义百度

/*多行
注释*/

2、操作符

简介:

+ - * / %
float a = 9 / 2; //9和2都是整数,执行的是整数除法,必须使其中一个为小数形式才行,比如9.0或2.0
% //取余数

>> << // 左右移操作符,以二进制为基础移动
int b = 2 << 1; //0...00010 ,向左移动一位——> 0...00100变成4,尾部补0,右移反之一样

& | ^ //按位与、或、异或//取反,比如0代表false,非0代表true,!0就代表真,!1就代表假

~ //按位取反,将某个数的二进制为,全部反转(包括符号位)
~0 = -1; // 00...0000 ——> 11...1111,二进制首位是符号位,1代表负,0代表正
 -1     // 10...001 原码
        // 11...110 反码(转成反码,符号位不变)
        // 11...111 补码(反码+1),内存存的是补码
 0的三码二进制全是0,补码取反后都是1,再按上面的方式转成原码就是-1
 正整数,三码相同

详细:

%   取模时,两端必须是整数,不可对0取模

>> 算数右移,左边补原符号,逻辑右移,左边补0

//都要是整数
&   按位与,都是1的时候取1,否则取0
|   按位或,只要有一个是1就取1,否 则取0
^   按位异或,不同取1,相同取0

3、循环、选择、条件判断符号

注意:许多函数返回值可以用来做判断true和false,0代表false,非0代表true,在写判断循环时,可以将某些函数直接用作循环的判断

条件判断符号

>= <= ==
&& 且,|| 或

三目表达式
exp1 ? exp2 : exp3
真取exp2,假取exp3

,逗号表达式
int d = (a = b+2, c = a+2, b = c+2); //从左至右,依次计算,最后b的值赋值给d

-> //结构体指针->结构体成员
struct Stu *ps = &s;
printf("%s %d",ps->name,ps->age);

if 选择

if (){
    xxx;
    }
else if(){
    xxx;
    }
else{}

(18 <= age < 20); //这种判断写法错误,当age是21时,18<=age其实就是1,1<20又是1,会执行后面的代码。
(18 <= age && age < 20); //这是正确写法

while、do while循环

while(){
    xxx;
}

do {
    xxx;//do会提前执行一次,再进循环
    i++;
}while (i <= 10);
break; //跳出循环
continue; //跳出当前循环进入下一次判断

switch 选择

int num = 0;
scanf("%d", &num);
switch(num)// 括号内的表达式必须是整型、字符
{
    case 1:
        xxx;
        break;//记得加break,不然会穿透下去执行
    case 2:
        xxx;
    default ://始终会最后执行的一条
        xxx;
}

for 循环

int i = 0;
for (i = 1; i <= 10; i++){ //int i = 1;这个写法需要gc99编译器等支持
    xxx;
}

for(; ;){} // 死循环写法

for (i = 1, y = 1; i <= 10 && y <= 10; i++,y++){ //多变量写法
}

4、关键字

auto    自动带入的,创建变量时就有,自动创建、自动销毁的意思,省略
extern  声明引用外部符号
register    寄存器关键字,计算机中数据存储位置有内存、硬盘、高速缓存、寄存器
    // register int num = 100; 建议num的值存放在寄存器,并不是必定,寄存器读取速度更快,编译器会自动识别将某些变量自动放入寄存器
signed  有符号
unsigned    无符号,比如数字的正负
static  静态修饰
struct  结构体
typedef  类型定义
union   联合体
volatile

defineinclude等不是关键字,是预处理指令

typedef,unsigned

typedef unsigned int u_int;
int main(){
    u_int num = 100; //通过typedef定义一个类型自己想组合起来的类型,用的时候就直接调用
}

static

1static修饰局部变量
void test(){
    static int a = 1;
    a++;
    printf("%d",a);
}
int main(){
    test();//如果a没有static,每次调用都是输出2
    test();//如果a被static修饰,第一次test函数结束后,a没有被销毁,a就变成了2,这次输出的结果是3
}
static修饰局部变量改变了变量的存储类型,内存分为堆、栈、静态区
栈:局部变量、函数的参数
堆:动态内存分配的
静态区:全局变量,static修饰的变量
没有static,test结束后a就销毁了,下次进入又是一个新的a
有static,test结束后a还在,并且值已被++

2static修饰全局变量
另一份文件:static int g_val = 100;
extern int g_val;//声明外部符号
int main(){
    printf("%d", g_val);//运行报错
}
static修饰全局变量后,除了原文件内可以使用,其它文件不能使用

3static修饰函数
extern int Add(int x, int y); //声明外部函数
int main(){
    int sum = Add(1,2); //没被static修饰时正常调用
    //当原文件的Add()被static修饰后,就无法调用了
}
static修饰函数后,只能在原文件内使用

define定义常量和宏

#define MAX 100
#define ADD(x , y) x+y
int main(){
    printf("%d", ADD(2 , 3)); //结果是5
    printf("%d", 4*ADD(2 , 3)); //结果是11,这里ADD=2+3——>4*2+3=11
}
规范写法:#define ADD(x , y)  ((x) + (y))
因为x和y可能是一个表达式,用括号括起来不被其它表达式、或计算影响
因为(x)+(y)是一个整体,在使用ADD()时是直接替换,不像函数,所以(x)+(y)也要被括号括起来以免被影响

5、函数

函数定义

//定义Add函数
int Add(int x, int y){
    int z = 0;
    z = x + y;
    return z;
}
int main(){
    int result = 0;
    int x = 1;
    int y = 2;
    int Add(int x, int y); //如果这个函数在main下面,需要先声明一下,x和y可以不用写,写上类型和个数就行,一般函数都是写入在头文件中,再引用头文件
    result = Add(x,y);//调用函数
}

函数参数

//数组传参
int Add( int arr[] ){ //数组名是首元素地址,传值的时候传数组名是只拿到了数组第一个地址
    xxx;
    //例外:
    //sizeof()使用传参前的数组名时,数组名表示的是整个数组
    //&arr 取地址时,也是整个数组的地址
}

6、结构体初阶

定义结构体

//结构体是对一些复杂事物的定义,相当于创建一个新的自定义类型
struct Stu{
    char name[20];
    int age;
} s1 , s2={ {'a'} , 11 }; // 这里可以直接创建名为s1,s2的此结构体,是全局变量,同时可以直接初始化值
int main(){
    struct Stu s = {"name",20};//创建一个定义的Stu结构体
    printf("%s %d", s.name, s.age);
    
    -> //结构体指针->结构体成员
    struct Stu *ps = &s;
    printf("%s %d",ps->name,ps->age);
}

7、指针

内存:每个内存单元是1个字节
32位:32根物理地址线,正电=1,负电=0
64位:同32位原理

0000...0000 ——> 1111....1111一共有232个地址,**232次方个内存单元**。

int a = 10; //地址是随机分配的,一个int是4个字节,32位。
十六进制展示:0xABCD..1,打印出来的是第一个字节的地址,依次是0xABCD..2、3、4
值也是十六进制展示:0a 00 00 00
0a表示是10,现实中写为:00 00 00 0a

指针变量

 //int类型的指针变量,如果控制此指针+1,那么会前进4个字节
 //比如原地址是0xABCD..1,如果pa+1就会变成0xABCD..5
int *pa = &a;  
//将地址解引用,找到a,并给a修改值
*pa = 20;

所有类型指针都是4个字节大小,因为32位架构的地址就是4个字节,64位则是8个字节

8、数组

数组的访问方式有 arr [n]和 n [arr]两种,[]是个运算符,所以两种方式可以,底层会自动转换

一维数组

一维数组每个元素的地址是连续存放的,随着下标增长,地址是由低到高的

int main(){
    //{0},当只有一个值时,其长度内所有元素都是这个值
    //当填入部分值时,其余值会默认为0
    //当没填写长度时,根据填入的值确定长度
    int arr[ num ] = {0}; // num必须是常量,C99语法才支持变量,俗称变长数组。
}

二维数组

二维数组,从[0][0]、[0][1]、、、[1][0]、[n][n]的地址是连续的

int main(){
    
    int arr [num][num] = {0};
    int arr [2][2] = {1,2,3,4}; //自动按顺序编排,1、2放一组,3、4放一组,没写的自动为0
    int arr [num][num] = { {0} , {0} };
}

9、程序设计

以一个三子棋为例子,无具体实现,主要记录程序结构

game.h{
    #include <stdio.h> //头文件内引入其它头文件,这样其它文件引入game.h时就不需要引入重复的头文件了
    //常量定义
    #define COL 3
    #define ROW 3
    
    //函数声明
    void InitBoard( char board[ROW][COL], int row, int col );
}
test.c{
    #include "game.h" //引用自定义的头文件,使用其内定义的东西
    void game(){
        //创建棋盘
        char board[ROW][COL];
        //调用初始化棋盘函数
        InitBoard(board, ROW, COL);
    }
    
}

game.c{
    #include "game.h"
    void InitBoard( char board[ROW][COL], int row, int col ){
        //初始化棋盘操作,给每个元素设置为' ',成为空棋盘
    }
}

10、指针初阶

取地址:int* pa =  &a
指针大小是固定的,32位系统是4字节,64位是8字节
不同类型的指针,代表着其步进长度,如char1个字节的大小,那么char*在+-的时候跨度为1个字节,也就是8位。
如:char* p的地址为:0x00...01,p+1则等于:0x00...02
野指针:
//1、指针未初始化,为随机值
int* p;
*p = 1; //无法执行

//2、数组访问越界也会造成野指针
arr[3] =  {};
int* p = arr;
*(p+3) = x; //此时访问第四个数组下标,则会随机指向一个地址

//3、指针指向的空间释放
int* test(){
    int a = 1;
    return &a;
}
int main(){
    int* p = test();
    *p = 1; //test函数结束后,a就被释放了,p再去访问则是一个不确定的地址
}

数组指针

int arr[5] = {0};
//必须是同一块空间的指针才能进行计算
&arr[4] - &arr[0] = 4; //数组指针与数组指针相减可以得到之间的元素个数

//C语言标准规定,【数组首位地址-1】不允许和其它数组元素地址比较,【数组末尾地址+1】允许和其它元素地址比较

指针数组

int main(){
    int arr[10]; //存放整形的数组->整形数组,存字符即是字符数组
    int* prr[10]; //存放整形指针的数组->整形指针数组
}

指针的指针(二级指针)

int main(){
    int a = 10;
    int *pa = &a;
    int* *ppa = &pa; // 将pa的地址存在另一个变量内,二级指针需要使用双**
    
    *ppa == pa;
    **ppa == a;  //双解引用
}

11、调试技巧

Debug:包含调试信息,没有任何优化,便于调试。
——debug编译后的exe比release大,包含了调试信息。
Release:进行了优化,程序代码大小和运行速度都是最优,便于使用。(比如数组访问越界到其它变量,release会进行优化避免越界到别的变量)

windows调试技巧:
——vs快捷键:
————F5进入调试,直接跳到断点处
————F9创建与取消断点,在鼠标所选行按下,鼠标左键点行号左边也可
————F10一次执行一个过程,比如一个函数或一条语句
————F11一次执行一个语句,可以进入一个逻辑内,如进入函数内部

启动调试,工具栏调试-->窗口内可以打开断点(可以查看所有断点)、监视(查看变量的变化)、自动窗口(涉及到当前步骤的变量会自动出现在窗口内)、局部变量(同自动窗口,少于自动窗口的变量)、内存(监测内存地址、十六进制值)、反汇编(翻译成的汇编代码)

断点条件:断点右键可以设置条件,在某个条件下停止到这个断点,如在循环内设置i==n的时候停止

12、数据的存储

整型

内存存的就是补码,计算时用的补码
//正整数:原、反、补相同
//负整数:原(=>二进制取反,符号位不变)反码(=>反码+1)补码

大小端存储
0x1122334444是低位,到11是高位)
//大端存储:低地址11223344高地址
//小端存储:低地址44332211高地址

浮点型

浮点型存储规则:
计算公式:-1^S * M * 2^E
以下解释是以32位为基础,64位的话换掉对应的值即可,如E的长度不同,对应更换中间值。
E是无符号数,所以没有负数,所以规定127是中间值,如果E是-1,那么存入E的是-1+127=126。
M在存储进去的时候,会把首位的1忽略掉,只存储后面的小数位,并且后面没有的补0
——32位
符号位:S (1bit),值是0/1
指数位:E (8bit)
有效数:M(23bit)大于等于1,小于2
——64位
符号位:S (1bit),值是0/1
指数位:E (11bit)
有效数:M(52bit)大于等于1,小于2

//代码解释存储
float f = 5.5f;
//101.1
//1.011 * 2^2
//S=0 M=1.011 E=2 这是用公式表示时各数据情况
//S=0 M=011 E=2+127 这是真实存在计算机内的值
// S:0  E:10000001  M:0110000000000000000...
//十六进制:40 b0 00 00
//小端存储:00 00 b0 40
//代码解释获取
//当E不为全0或者全1,依照存储时的方式反向取出
//当E为全1,如果M全为0,表示正负无穷大的数,取决于符号位
//当E为全0,表示接近0的很小的数字,取出时M不会加上首位去掉的1,而是就取0,E如果是-127那么存进去就是-127+127=0,取出时用1-127
例子:
int n = 9;
//000000000000...1001
float* pFloat = (float)&n;
%d,n —输出结果—> 9
%f,*pFloat —输出结果—> 0.000000
//以浮点数方式拿
//0         00000000                    000...1001
//S=0       E=1-127               M=0.000000..1001
//1 * M * 2^E = 无限接近0的数,打印出来就是0.000000

*pFloat = 9.0;
//1001.0
//1.001 * 2^3  E=3
//0 10000010 00100000....0
%d,n —输出结果—> 将上面的二进制数转成了一个很大的十进制数
%f,*pFloat —输出结果—> 9.000000 浮点转浮点,值不变

13、指针进阶

数组指针+数组指针数组

指针数组(存放指针的数组)
int* arr[1]; //存放整形指针

数组指针(指向数组的指针)
int arr[1] = {1};
int (*parr) [1] = &arr; // $arr是数组的地址,arr是首元素地址
//*parr是变量为parr的指针,里面存放的是int类型数据,一共是10个int数据

数组指针解引用
int* arr[1] = {&i}; // 存放int指针的数组arr
int* (*parr) [1] = &arr; //指向上面数组的指针parr
*(*parr + 1); //*parr解引用拿到的是arr首元素地址,首元素地址+1再解引用拿到的是第二个元素值

int arr[2][3]; //二维数组
//arr代表第一个元素,第一个元素就是二维数组里面的第一个数组的地址。
//函数定义入参时,应当写成  int (*arr) [3] ,此时代表接收的是一个int类型3长度的数组地址
//入参还可以直接使用数组方式定义,int arr[2][3] ,此时传值也是首数组元素的地址

数组指针数组(存放数组指针 的  数组)
int ( * ) [2]; //这是一个数组指针,没有变量名,指向是int类型,2长度的数组
//接下来再定义一个数组,里面存放上面的数组指针
int ( * arr[3] ) [2];
//拆开来解释:
// arr [3] 是一个长度为3的数组
// 上面说过,int ( * ) [2] 是一个数组指针
// 那么arr[3] 里面 要存 int (*) [2]的数组指针
//最后得出, int (* arr[3] ) [2], arr[3]三个元素,每个元素都是int (*) [2],int (*) [2]指的是int类型长度2的数组地址。
核心思想是拆开来看,以最基础的int arr [3], arr是名字,[3]代表是数组,int代表是类型
那么上面的数组指针数组,   arr是名字,arr[3]是数组,类型是int (*) [2]

数组传参

一维数组传参

//下面是前提条件和调用传参,忽略下写的顺序和格式
int arr[1] = {0}; 
test (arr);
void test ( int arr[] ){} //OK——形参不需要长度,可以写但没必要
void test ( int *arr ){} //OK——arr代表第一个元素地址,即 整形指针

int *arr2[1] = {0};
test2(arr2);
void test2( int *arr[] ){}//OK——arr2就是指针数组首地址,接收也是指针数组地址
void test2 ( int **arr ){}//OK——arr2是首元素地址,首元素又是一个int*,那么arr2本身就是一个二级指针

二维数组传参

int arr[3][5] = {0};
test(arr);

void test( int arr[][5] ){} //OK——这里要注意,二维长度必须要写明确,一维可以省略也可以写上
void test( int *arr ){} //不OK——因为arr是代表的int arr[5],这是第一个元素,是个整形数组
              int* arr[5] //不OK——这是要接收一个指针数组
              int (*arr)[5] //OK——这里接收一个int型5长度的数组指针,而arr就代表首元素,就是第一个数组的地址,第一个数组的地址那么就是数组指针
              int **arr // 不OK——同上一个,arr指的是第一个数组的地址,他是数组指针,不是二级指针(二级指针里面存的是一级指针的地址)
posted @   小小鸡炖蘑菇  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示