C语言-基础

参考

https://www.runoob.com/cprogramming/c-tutorial.html

变量

对于不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为NULL(所有字节的值都是0),其他所有变量的初始值是未定义的
需要建立存储空间的变量变量在声明的时候就已经建立了存储空间
无需建立存储空间:通过extern关键字声明变量名,而不定义他,这种可以在别的文件中定义

变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储
声明与定义???
extern int i 是声明,不是定义
int a 是声明,也是定义

左值/右值???

左值:指向内存位置的表达式, 叫做左值表达式
右值:内存中某些地址的数值

字面量/常量

字面量:常量 – 不允许进行修改
整型常量、浮点常量、字符常量、字符字面值、枚举常量、定义常量

定义常量

  • #define 预处理器
  • const 关键字
// #define
#define Variable value // 注意没有分号 可以定义字符型、字符串型的常量

// const
const type variable=value; // 注意有分号,且必须直接赋值

变量作用域

main函数也是函数,main函数中定义的变量也是局部变量

  • 在函数或块内部的局部变量

在某个函数或块内部声明的变量称为局部变量,只能被函数或该代码块内部的语句使用

  • 在所有函数外部的全局变量

全局变量在整个声明周期内都是有效的,在任意的函数内部都能访问全局变量

  • 形参

被当做函数内的局部变量

命名冲突

局部作用域内,优先使用局部变量

全局变量与局部变量

全局变量:保存在内存的全局存储区中,占用静态的存储单元
局部变量:保存在栈中,只有在所在函数被调用时才动态的为变量分配存储单元

初始化局部变量和全局变量

局部变量被定义时,系统不会对其初始化(或随机初始化,根据编译器的不同策略不同),必须手动对其初始化
全局变量被定义时,系统会自动对其初始化,默认是0,(因为全局变量存储在内存分区中的全局数据区,这个区域中的数据在程序载入内存后会初始化为0)
在函数内部修改全局变量会影响其他函数,

int 0char-\0float 0double 0pointer NULL

未初始化的变量会导致一些在内存位置中已经可用的垃圾值

作用域扩展 python

a=1
b=2
c=4
def f():
    print(c)
    c=a+b
f()
print(c)
# c为局部变量,会报错!!!

数据与地址

数据以二进制的形式保存在内存中,字节是最小的可操作单位
在内存管理中,为每个字节分配了一个编号,使用该字节时,只要知道编号就可以,这个编号,就是地址

获取地址

&data,(data可以是数值、字符)

存储类

存储类:定义c程序中变量/函数的范围(可见性)和声明周期
说明符,放在所修饰类型的类型之前

  • auto
  • register
  • static
  • extern

auto

所有局部变量的存储类,只修饰局部变量,只用在函数内

int func()
{
	// 下面两种写法都表示的是局部变量
	int mount;
	auto int month;
}

register

定义存储在寄存器中而不是RAM中的局部变量,意味着变量的最大只存等于寄存器的大小(一个字节),且不能也对它应用一元运算符???
寄存器只用于需要快速访问的变量,如计数器等(定义了register,并不意味着变量将被存储在寄存器中,只是意味着变量可能存储在寄存器中,这取决于硬件和实现的限制)

int func()
{
	register int miles;
}

static

https://www.runoob.com/w3cnote/cpp-static-usage.html – 还没看

指示 编译器 在 程序 的生命周期内 保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁,因此使用static修饰局部变量可以在函数调用之间保持局部变量的值
static也可以修饰全局变量,当修饰全局变量的时候,会是变量的作用域限制在声明它的文件中。全局声明的一个static变量或方法可以被任何函数或方法调用,只是必须与被修改的变量或方法在同一个文件中

static var 表示var的作用域为当前的文件,其他文件无效

int func()
{
	// num 是局部变量,但是只被初始化一次,每次调用函数func的时候,num不会被重置
	static int num=10
	num--;
}

extern

提供一个全局变量的引用,全局变量对所有程序文件都是可见的
使用extern无法初始化变量,会把变量名指向一个之前定义过的存储位置
其实就是 A文件中定义了一个可以在B文件中使用的全局变量或函数,那么在B文件中就可以使用extern来得到A文件中定义的变量或函数的引用(用在在另一个文件中声明一个全局变量或函数)
进阶:A/B文件可以存在相互的使用extern

// A.c
#include <stdio.h>
int count;
extern void write_extern();

int main()
{
	count=5;
	write_extern();
}
// B.c
#include <stdid.h>
extern int count;
void write_extern(void)
{
	printf("count is %d\n",count)
}

其他关键字

struct 结构体
enum 枚举类

typedef

为数据类型起别名,但是不创建新的类型
typedef oldName newName

typedef int INTEGER;
INTEGER a,b;

// 给数组起别名
typedef char ARRAY[10];
ARRAY a1,a2; // 等价于 char a1[10],a2[20];


// 给结构体类型定义别名
typedef struct stu{
	char name[20];
	int age;
} STU;
STU s1,s2; // 等价于 struct stu s1,s2;

// 给指针类型定义别名
typedef int (*PTR_TO_ARR)[4]; // 二维数组指针

typedef int* PTR_INT
PTR_INT p1,p2;

const

整个作用域值不被改变,常量

const int Var=10;

const 与指针

可以限制指针变量本身,也可以限制指针指向的数据
离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据

不要将const修饰的数据 赋值给非const修饰的数据,不然会报错

const int* p1; // 指针指向的数据是只读的,p1,p2本身的值可以被修改,即可以存不同的地址,即指向不同的数据,但是指向的数据不能被修改
int const * p2; // 指针指向的数据是只读的,p2与p1的效果类似
int* const p3; // 指针是只读的,即p3本身的值不能修改(p3存的是地址)

// 下面的方法 表示指针和指向的数据都是只读的
const int* const p4
int const * const p5

const 与函数

单独定义常量 可以直接用#define
const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,可以用const来限制

// 限制内部函数对字符串str进行修改
size_t strnchr(const char* str,char ch){
    int i,n=0,len=strlen(str);

    for (i=0;i<len;i++){
        if (str[i]==ch){
            n++;
        }
    }
    return n;
}

int main(){
    char* str="http"; //hello
    char ch='t';

    int n=strnchr(str,ch);

    printf("%d\n",n);

    return 0;
}

数据类型

  • 基本类型
  • 枚举类型

只能被赋值一定的离散整数值变量

  • void类型

类型说明符,表明没有可用的值

  • 派生类型

指针类型、数组类型、结构类型、共用体类型和函数类型
其中:函数类型指的是函数返回值的类型

基本类型

整数类型

  • char

1个字节,[-128,127] 或[0,255]

  • unsigned char

1个字节,[0,255]

  • int

2或4字节,(win 4字节)

  • unsigned int

2或4字节

  • short/unsigned short

2字节

  • long/unsigned long

4字节

  • double

8字节

前缀:表示进制关系
后缀:表示有符号(默认)和无符号整型U、长整型L,大小写任意,顺序任意

有符号和无符号

当以有符号数的形式输出时,printf会读取数字所占用的内存,并把最高位作为符号位,把剩下的内存作为数值位,但是对于有符号的正数而言,最高位恰好是0,所以可以按照无符号数的形式读取,且不会有任何的数值影响
当以无符号的形式输出时,printf也会读取数字所占用的内存,并把所有的内存都作为数值位对待

进制数
0b 0B # 二进制数 
0x 0X # 十六进制数
0 #八进制

注意标准编译器不支持0b的二进制写法,只是某些编译器在扩展后支持
注意printf 输出形式

整型只控制字节长度,和数值范围
进制数才是实际数值的表现形式
一个数字不管以何种进制来表示,都可以 以任意进制的形式输出,因为数字在内存中始终以二进制的形式存储
其他进制的数字在存储前都必须转换成二进制形式,而在输出前也需要反转

原码/补码/反码

原码:数值的二进制数,包括符号位
反码:

正数的反码=原码
负数的犯法=符号位不动,其他位置取反

补码:

正数的补码=原码
负数的补码:是反码加1

-128的补码就是1000 0000
补码 1000 0000 就表示-128 记录就可以了
深究里边,是因为符号位会被数值的高位进行多次覆盖

计算机内存中,整型一律采用补码的形式存储,因此读取整数时还要采取逆向的转换,也就是将补码转换为原码

int 与 char

https://blog.csdn.net/u012782268/article/details/40021887

数值溢出

浮点类型

  • float

4字节,32位

  • double

8字节,64位
对于小数默认是double类型

  • long double

16字节

float x=1.2 # 赋值的时候,会先从double类型转换为float类型

# 后缀
float x=1.2f # 赋值的时候直接是默认的float类型

注意所有的浮点型都是以双精度double进行运算的,即使是float类型,也要先转换成double类型

<float.h>定义了宏,可以在程序中使用这些值和其他有关实数二进制表示是的细节

#include <float.h>

FLT_MIN # 最大值
FLT_MAX # 最小值
FLT_DIG # 精度值

字符型

单引号
charunsigned char 是一个字符类型,用来存放字符,但是同时也是一个整数类型,也可以用来存放整数!!!,注意取值范围
char 1个字节,[-128,127]
unsigned char 1个字节,[0,255]
char 类型只能存储ASCII字符,不能存储其他字符根据上面的定义,也可以存放范围以内的进制数

字符与整数

由于char类型只能存储ASCII字符,字符集及其编码其实本质上就是二进制数,(本质上与整型没有区别)
定义一个char类型字符,会先转换成ASCII字符对应的编码再存储
而存储一个整型的时候,不需要任何转换直接存储
由于数据类型不同,所以需要的字节位数不同,因此在存储和读取的时候占用的内存不一样,所以在读取 字符型和整型 的时候并不会有冲突

字符与进制数/字符型进制数

字符型与进制数

由于char可以存放整型,所以可以声明和定义取值范围内的进制数
下面的例子中,就可以把char想象成整型(但是要注意取值范围)

char a=0x32 # 
printf("%c",a) // 2
printf("%d",a) // 50 这是因为由十六进制转换为的十进制 

// 与python类别
// '2'.encode().hex() # 32
// int('32',16) # 50
// chr(50) # '2'

对于char类型的字符,(由于是窄字符,且只占一个字节,所以可以使用ASCII编码),(又由于ASCII编码的结果就是一个二进制数),可以利用转义的进制数进行表示
下面的例子,就可以把其当成字节对象(字节串对象),想当于python中的bytes对象,

char a='\x31' // a是字符数值1
char b='\x61' // b是字符a

char *str1="\x31\x32\x33\x61\x62\x63" // 字符串"123abc"

// 类别python
// '123abc'.encode().hex() # '313233616263'
putchar/getchar

putchar输出字符
getchar接收字符

char a=getchar();
转义字符
printf("\"string\'") // "string'
中文字符

对于中文字符应该使用宽字符的形式,并加上前缀,加上前缀后,所有的字符都变成了宽字符的存储形式
wchar_t类型,在不同的编译器下对不同的语言使用不同的长度

#include <wchar.h>
wchar_t a=L'中'
宽/窄字符(串)

char类型的窄字符,使用ASCII编码
char类型的窄字符串,通常来讲是UTF8编码,(其实不同的编译器在不同的平台上编码方式不同)
wchar_t类型的宽字符或宽字符串,使用UTF16或32编码

见文档

字符串型

双引号
在内存中占一块连续的内存
其实字符串数据,依靠的是数组和指针来简介的存储字符串

char s1[]="string1";
char *s2="string2"

char s1[]="string"

这种方法定义的字符串所在的内存既有读取权限又有写入权限(可变),可以用于输入与输出函数
[]可以指明字符串的长度,如果不致命则根据字符串自动推算
声明字符串的时候,如果没有初始化,(由于无法自动推算长度,只能手动指定)char s[10]

char *s1="string"

这种方法定义的字符串所在的内存只有读取权限,没有写入权限(不可变),只能用于输出函数

puts/gets

puts输出字符串
gets接收字符串,与scanf的区别见下

char s[10];
gets(s)

sizeof

获得当前平台上的对象或类型的存储字节大小

printf("%lu",sizeof(int))
printf("%d",sizeof(instance))

数据类型转换

数据类型存在强制转换
原则

浮点数赋给整型:浮点数小数部分会被舍去(注意不是四舍五入)
整数赋值给浮点数,数值不变,但是会被存储到相应的浮点型变量中

在这里插入图片描述

强制转换

注意,强制转换是临时性的,并不会影响数值本身的数据类型和值

(类型说明符)(表达式)
(int)(x+y)
整型与字符型运算
int a=10;
char b='a';
printf("%c\n",b+a); // 'k'
printf("%d\n",b+a); // 107

基本运算

±*/

下面的例子中 由于都是int类型,所以即使是算出了float值,也会变成int类型,只不过在printf变成了对应的格式
因此必须在先前就想结果类型

int a=1;
int b=2;
float c;
c=a/b;
printf("%f",c); // 0.000000
/ %

%取余运算,只能作用于整型,不能作用于浮点型

注意:正负数,只根据%左边的数值符号决定!!!

++/- -
  • ++ 自增
  • -- 自减
num++; // 先运算后+1
num--; // 先运算后+1

//例子
v=num++; // v=num;num+=1
++num/num++
num++; // 先运算后+1
++num; // 先+1后运算
逻辑运算

逻辑运算符的结果 是 0 1
并不是bool类型

  • &&and
  • ||or
  • !not

注:与python并不一样,python在逻辑运算上,对数值和bool类型的计算结果不一样

位运算
  • &
  • |
  • ^ 异或
  • ~ 取反
  • << 左移
  • >> 右移
? :

如果条件为真?则值为x:否则值为y
优先级:算术运算符>关系运算符>?:
结合性:从右到左

关系运算符

关系运算符的结果 是 0 1
并不是bool类型

运算优先级

注意结合性
同级别具有左结合性
! 大于 算数运算符 大于 关系运算符 大于 逻辑运算符中的&& 和 || 大于 赋值运算符

void 类型

函数返回为空
函数参数为空,不接受任何参数,int main(void)
指针指向void:类型为void*的指针 代表对象的地址,而不是类型

printf

https://www.runoob.com/cprogramming/c-function-printf.html – 没看完
http://c.biancheng.net/view/1793.html?from=pdf_website_1_0 – 没看

%

输出格式
%[flag][width][.percision]type
width 控制输出宽度,数值不足宽度,空格补齐;数值或字符超过宽度,width不再起作用,按照数据本身宽度来输出;(例如%12f默认保留6位小数,要输出12个字符宽度);width可以用于数值、字符、字符串
.precision 控制输出精度,小数位数大于precision,按照四舍五入输出;当小数位数不足precision后面补0
.precsion 与width不同,作用于整型的时候,不足宽度在左侧补0(作用于浮点数只控制小数位数);对于控制字符串输出,字符串长度大于.precision则被截断
总结,width要么补齐空格,要么不起作用;.precision对整型左侧补0或不起作用,对于浮点小数位补0或四舍五入,对于字符串被截断或不起作用

%d //十进制有符号整数
%u //十进制无符号整数
%f //float浮点数,默认保留六位小数,不足六位以0补齐,超过六位按四舍五入阶段
%lf //double
%c //字符
%s //字符串
%p //指针的值
%e //指数形式的值
%x //十六进制 无符号

%lu //32位无符号整数
...

// printf的高级写法,用于区分不同的进制数字
print("a=%#hx",0x1d)

整型、短整型、长整型,分别有对应的printf输出形式
printf中不能输出二进制数
需要某些转换函数

一个数字不管以何种进制来表示,都可以 以任意进制的形式输出,因为数字在内存中始终以二进制的形式存储
其他进制的数字在存储前都必须转换成二进制形式,而在输出前也需要反转

在这里插入图片描述

注意事项

有符号和无符号
当以有符号数的形式输出时,printf会读取数字所占用的内存,并把最高位作为符号位,把剩下的内存作为数值位,但是对于有符号的正数而言,最高位恰好是0,所以可以按照无符号数的形式读取,且不会有任何的数值影响
当以无符号的形式输出时,printf也会读取数字所占用的内存,并把所有的内存都作为数值位对待
因为通过格式控制符进行数值输出的时候,其实并不会检查定义的数值是有符号还是无符号数,只会按照格式控制符对数值进行特定的解释

转义字符与特殊字符

"\\"%%

prinf与缓冲区

通常来说,printf执行结束后数据并没有直接输出到显示器上,而是放入了缓冲区,**注意:**不同的操作系统,对于printf和缓存机制是不同的

printf 字符串

字符串类型,有两种定义方式,对应在内存中有两种 读取与写入权限
在输出时,要求字符串只需要有读取权限

scanf

从控制台读取数据
根据下面的例子,scanf会根据地址把读取到的数据写入内存
注意:多输出时的结果,除了空格外,必须严格输入
注意:超额输入的时候,不会出错,相当于位置参数(这是因为缓冲区的存在
从键盘输入的数据并没有直接交给scanf,而是放入了缓冲区中,直到回车,scanf才到缓冲区中读取数据
输入可以不是指定的类型,但是会存在数值转换的风险

int a;
scanf("输入图像",&a)

// 多输入
int a,b;
scanf("%d sb %d",&a,&b) // 输入的时候,必须是'1 sb 2',不然会出错

// 超额输入
int a,b;
scanf("%d %d",&a,&b) // 输入的时候,必须是'1 2 3',不会出错,只会使用之前的内容

// 连续多输入
int a,b;
int c,d;
scanf("%d %d",&a,&b);
printf("%d sb, %d\n",a,b);
scanf("%d %d",&c,&d);
printf("%d sb, %d",c,d);
// 输入的时候,可以是 1 2 3 4 ,也能正确输出 <-- 这是因为缓存区的存在

继续读取或读取失败

int main(){
    int a,b;
    int c,d;
    scanf("%d %d",&a,&b);
    printf("%d sb, %d\n",a,b);
    scanf("%d %d",&c,&d);
    printf("%d sb, %d",c,d);
	
	return 0;
}
// 输入 1 2 回车
// prinf()... 然后等待输入
// 输入 3 4 回车
// printf()... 结束

//输入 1 2 a10 (第三个是一个不符合要求的数据)
// 直接结束,上下的 c,d 显示的系统默认的初始值

int a=1;
int b=2;
scanf("%d %d",&a,&b);
printf("%d sb, %d\n",a,b);
// 输入 1 a10
// 不会出错,b输出的是初始值(和上面的例子一样)

scanf 字符串

字符串类型,有两种定义方式,对应在内存中有两种 读取与写入权限
在读取时,要求字符串有写入权限,因此只能用char s[]="string"这种形式
scanf在读取字符串的时候,在输入时,以空格当做输入结束标志

char s[30];

scanf("%s",s) // 这里不需要取地址符&
scanf/gets

getsscanf同样具有缓冲区,区别在于gets以回车作为字符串结束响应,而不是空格。scanf是以空格作为结束响应,所以不能读取包含空格的字符串

位运算

位运算 是根据 内存中的二进制位进行运算的,而不是数据的二进制形式,

&

按位与运算,通常用来对某些位清0,或保留某些位

|

按位或运算可以用来将某些位置1,或保留某些位

^

按位异或可以用来将某些二进制位反转

~

单目运算符,具有右结合性

大小端

数据低位

1234中34就是低位

大端

数据的低位放在内存的高地址上

小端

数据的低位放在内存的低地址上

<<

把各个二进制位全部左移若干位,高位丢弃,低位补0(小端模式下)(这里的高位和低位说的是数据高位和低位)

>>

把各个二进制位全部右移若干位,低位丢弃,高位补0或1小端模式下)(这里的高位和低位说的是数据高位和低位)
如果数据的最高位是0,就补0,;高位是1,就补1

控制方法

if-else

注意,else总是与其前面位置最近的if配对
判断语句中,可以是数值、表达式、赋值表达式、变量等,只要是非0都可以

if (condition)
{
	...
}
else if (condition)
{
	...
}
else ()
{
	...
}

//例子
if (a=b) // 把b的值赋值a,如果b为非0,则执行
{
	//TODO
}

switch-case

switch-expression 必须是一个常量表达式,一个整型(整型表达式)或一个枚举类型,(其实字符常量也可以,因为字符和整型可以相互转换),(也就是说不能包含任何变量,浮点型也不行
expression1/2 必须是与expression类型相同的数据,且必须是常量或字面量(字符也行,可以与整型相互转换
expression 总结

switch后面接的expression 可以是变量表达式(可以包括运算公式),但是必须是整型变量表达式
case 后面接的expression 必须是常量表达式(表达式的结果必须是整型就可以,但不能是变量)(可以包括运算公式)(不能是变量),必须与switch后的expression具有相同的类型
两种表达式不能是浮点型,但是可以是字符型

如果case不加break,控制流会继续后面的case,直到遇到break
当所有的case都不匹配的时候,有一个可选语句default(default不是必须的),default语句中的break不是必须的

swtich(expression)
{
	case expression1:
		...
		break;
	case expression2:
		...
		break;
	default:
		...
}

while

while(condition)
{
	...
}

for

int会首先被执行一次,且只会被执行一次;这一步可以声明并初始化任何循环控制变量,也可以不写任何语句,只有一个分号即可
然后会判断condition,如果为真则执行循环主体,如果为假,则不执行循环主体,且控制流会跳转到紧接着for循环的下一条语句
执行完for循环主体后,控制流会跳回increment,该语句可以留空,只要在条件后有一个分号出现即
条件再次被判断,如果为真,则执行循环,条件变为假的时候,for循环终止

for(int;condition;increment)
{
	...;
}

do-while

函数体至少会被执行一次

do
{
	...;
}while(codition);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值