c语言学习笔记

C语言

1//struct 创建一个自定义的类型

struct Book

{

//结构体的成员(变量)

char name[20];

char id[20];

int price;

};

int main()

{

int num = 20;

struct Book b = { "cyuyan","c121",55 };

//结构体变量名.成员名

// "."是调用操作符

// 这里用printf("%s\n", b.name);可以

//或者我们取地址

struct Book* pb = &b;

//这里用printf("%s\n",(*pb).name);可以

//或者用"->"操作符也可以

printf("%s\n", pb->name);

//用法:结构体指针->成员名

return 0;

}

2int main()

{

char a = 3;

char b = 127;

char c = a + b;

//打印出来结果是 -126

//char 没有达到int的大小,所以这里发生了整形提升

//整形提升,高位补充符号位unsigned高位补充0

//这里c是char类型,相加后也只能读取8位,然后对c进行整形提升

//前面补了1,然后一顿操作之后变成了 -126

printf("%d", c);

//int c=a+b;打印出来是130;

return 0;

}

Char short 参与运算会整型提升,增加精度

3

int main()

{

int a = 1;

int b = 2;

short c = 1;

short s = 0;

//这里打印的是s类型的大小

printf("%u\n", sizeof(s = c - b));

return 0;

}

4

int main()

{

//这样的代码是非法的,最好把运算的每一步拆开,或者加上括号

int i = 0;

i = i++ * --i * i * i;

printf("%d", i);

return 0;

}

5

//这样的代码很有问题,不要写出来

int fun()

{

 static int count = 1;

 return ++count;

}

int main()

{

int answer;

//这些符号如“-”或“*”都有优先运算的顺序

answer = fun() - fun() * fun();

printf("%d", answer);

return 0;

}

6

//一个指针大小在32位平台上是4个字节,64是8个字节

//不同指针类型大小都一样

int main()

{

int* pa;

char* pc;

float* pf;

printf("%d",sizeof(pa));

printf("%d",sizeof(pc));

printf("%d",sizeof(pf));

return 0;

}

7

//指针类型的意义

//1.指针类型决定了:指针解引用权限有多大

//2.指针类型决定了走一步能走多远(步长),char*走一步(+1)是一个字节,int*走一步是四个字节

int main()

{

int a = 0x11223344;

int* pa = &a;

pa = 0;

//pa变成0x00000000

//这里如果是

/*

char* pc = &a;

pc = 0;

则,不能改变所有数字变成0x00332211

*/

return 0;

}

int main()

{

int arr[10] = { 0 };

int* p = arr;

    //数组名代表首元素的地址

char* pc = arr;

printf("%p", p);

printf("%p", p+1);

printf("%p", pc);

printf("%p", pc+1);

return 0;

 }

8

int main()

{

int arr[10] = { 0 };

int* p = arr;

//进行定义的时候最好正确定义,这样才好写

for (int i = 0; i < 10; i++)

{

*(p + i) = 1;

}

return 0;

}

9

//野指针

int main()

{

//这里的p就是野指针

int* p;//p是一个局部变量的指针,并未初始化

*p = 20;//这就是非法访问

return 0;

}

10

int main()

{

int arr[10] = { 0 };

int* p = arr;

int i = 0;

//这里,p到最后一个循环会变成一个野指针

//叫做越界访问

for ( i = 0; i <=10; i++)

{

*p = i;

p++;

}

return 0;

}

还有可能,在空间开辟这个环节产生野指针

11

//这里,将p=20传入给p赋好的a的地址,但是a已经被销毁了,所以再传就是非法访问,变成野指针

int* test()

{

int a = 10;

return &a;

}

int main()

{

int* p = test();

*p = 20;

return 0;

}

12

//如何规避野指针

//1.记得指针初始化

//2.不知道如何初始化的时候,初始化为空值NULL(本质上还是0);

//3.指针使用之前检查其有效性

int main()

{

int* p = NULL;

int a = 12;

int* ptr = &a;

//c语言本身不会检查数据的越界行为

//这样可以保证指针不会出错

int* c = NULL;

if (c!=NULL)

{

*c = 20;

}

return 0;

}

13

//指针运算

//1.指针加减

//2.指针比较

//3.指针与指针的加减(指针减去指针得到的是指针之间元素的个数,前提是两个指针指向同一个空间)

14

//这里函数传入的是字符串的第一个字符的地址,加一之后就是下一个所以,接收部分,用char*

int my_strlen(char*str)

{

char* start = str;

while (*start!='\0')

{

start++;

}

return (start - str);

}

int main()

{

int len = my_strlen("abc");

printf("%d", len);

return 0;

}

15

//指针

//1.c语言允许指针与指向数组最后一个元素的后一个进行比较,但是不允许其与第一个元素前一个指针进行比较

//2.*是解引用操作符,使用这个操作符地址进行访问数据

//3.

16

int main()

{

int arr[10] = { 0 };

int* p = arr;

//[]在这里是操作符,2和arr可以看作两个操作数

printf("%d\n", 2[arr]);

printf("%d\n", arr[2]);

printf("%d\n", p[2]);//这里,可以转化为*(p+2)

return 0;

}

17

//解引用时*ppa=pa **ppa=a

int main()

{

int a = 10;

int* pa = &a;//pa是指针变量,一级指针

//ppa就是一个二级指针

    int** ppa = &pa;//pa也是一个变量,&pa取出pa在内存中的起始地址

return 0;

}

18

int main()

{

//指针数组

int arr[20];

int* parr[20];

char ch[5];

char* pch[5];

return 0;

}

19

//数组:一组相同类型的元素的集合

//结构体:也是一些值,不过类型可以不同

struct B

{

int x;

char y[10];

};

struct Stu

{

//成员变量

struct B sb;

char name[20];

int age;

char id[20];

}; //s1, s2;//在末尾这样写,创建两个全局的结构体变量

int main()

{

struct Stu s ;//这样也是创建一个结构体变量(对象)

struct Stu ss = { {1,'a'},'a',6,2023};//对变量进行初始化//结构体嵌套初始化

printf("%d", ss.sb.x);//.对成员进行访问

struct Stu*ps=&s;

    Printf(%d,(*ps)->sb->s);

return 0;

}

20

struct s_tu

{

char s_id[8];

char s_name[8];

char s_sex[4];

int s_age;

};

//这样将结构体原本的地址传入比较好,不然会产生新的地址,占用内存

void print1(struct s_tu* ps)

{

printf("%d", ps->s_age);

}

int main()

{

struct s_tu s = { "190601","张三","man",18 };

//printf("%d", s.s_age);

print1(&s);

return 0;

}

21

//函数栈帧的创建和销毁

//栈,是一种数据结构,先进的后出,后进的先出,一个一个把数据放进去叫压栈

int a_dd(int x, int y)

{

int z = 0;

z = x + y;

return z;

}

//每一个函数的调用都会在内存的栈区开辟一块空间

//这片栈区数据会先后进去,传参的过程可以看作压栈操作

//传参调用之后,原先开辟的空间会消失,如果传参较大,会导致压栈空间很大,性能下降

int main()

{

int a = 3;

int b = 0;

int sum = a_dd(a, b);

return 0;

}

22

//计算机史上第一个bug是一只飞蛾

//调试技巧:

//debug被称为调试版本,包含调试信息,不做任何优化 release被称为发布版本,对代码进行优化,使其在代码大小和代码速度上都是最优的

//F5启动调试,经常用来跳到下一个断点处

//F9设置和取消断点

//F10一步一步地进行下面的代码

//F11和F10相同,但是可以进入函数内部

//CTRL+F5开始执行不调试,让程序直接运行不调试

//Fn辅助功能键,辅助上面的功能

//debug-windows里面有很多nice的功能

//调用堆栈里面是函数的调用逻辑

//局部变量放在栈区上

//栈区内存的使用习惯:先使用高地址空间再使用低地址空间,所以变量创建时近距离放置

//数组随着下标的增长,地址由低到高变化

23

//这个代表的意思是,二者相等之后,再各自加一

*ps++ = *as++;

24

//这里,括号里面是对str进行判断,str等于0的时候,停止

//*dest和*str分别代表一个数组传过去的值

while (*dest++=*str++)

{

;

}

25

//自我实现strcpy函数

void my_strcpy(char*dest,char*str)

{

while (*dest++=*str++)

{

;

}

}

int main()

{

char arr1[20] = "xxxxxxxxxxxxx";

char arr2[20] = "hello";

my_strcpy(arr1, arr2);

//这里用strcpy打印出的是hello,因为copy之后,会把\0也拷贝进去

printf("%s\n", arr1);

return 0;

}

26

int a = 3;

int b = 5;

//这个表达式,赋给a是8,表达式的结果就是8

int c = a = b + 3;

27

#include<assert.h>

//断言,如果括号内为假,就会报错

assert(str!=NULL);

28

//const 修饰变量,这个变量就被成为常变量,不能被修改,本质上还是变量

int main()

{

const int num = 0;

//const 在左边,修饰的是指针指向的内容,不能通过指针来改变内容

//但指针变量本身pn是可以被修改的

const int* pn = #

pn = 0;

//如果const放在右边,去修饰pn,则指针变量本事不能被改变不能被改变

return 0;

}

29

//c语言:

//可以分为三个文件

//1 test.c:用于主函数和主逻辑的编写

//2 game.c:用于自拟函数的实现

//3 game.h:用于函数的声明和在tese.c中的引用

30

//整形家族

// unsigned(无符号) signed

//浮点型家族

//构造类型——自定义类型

//  数组

//  struct 结构体类型

//  enum 枚举类型

//  union 联合体

//

//  指针类型

//  好多

//  空类型 void

//  函数的返回类型void test()

//  函数参数void test(void)

//  指针 void*p

31

//整数在内存中以2进制的形式存储

//对于整数来说:其二进制有三种表示形式:原码 补码 反码

//正整数:三者相同

//负整数:三着需要计算

//最高位是符号,不变,其余位置进行变换得反码,反码加一得补码

//整数在内存中存储的是补码

//用补码进行计算,补码也可以算

//CPU里面只有加法器,补码和原码的相互转换运算过程相同,不需要额外的硬件电路

32

//大端字节序和小端字节序

//把数据的低位字节序的内容存放在高地址处,高位字节序的内容存放在低地址处叫大端字节序

//比如0x11223344,11 22 33 44 按顺序来是高位到低位

//用代码来检验机器是大端字节序还是小端字节序

int main()

{

int a = 1;

char* p =(char*) & a;

if (*p==1)

{

printf("小端");

}

else

{

printf("大端");

}

return 0;

}

33

//c语言里 char没有明显规定是unsigned还是signed,这取决于编译器

//但int是 signed int short是 signed short

int main()

{

char a = -1;

unsigned char b = -1;

signed char c = -1;

printf("%d\n%d\n%d", a, b, c);

//结果是-1 255 -1 ,由于整形提升。无符号的b高位补零,得到255

return 0;

}

34

int main()

{

char a = -128;

//这里会把a的补码直接翻译出来

printf("%u\n", a);

return 0;

}

35

//有符号的char的取值范围是-128到127,最高位是符号位所以char类型不断增加的同时会循环-1 -2 -3...-128 127 126...1 0 -1 -2

11111111(-128),再加一,变成 100000000,向右移一位变成10000000,补码,变原码得127

//二进制的第一位是1,则是复数,为补码,编译时会变成原码,unsigned还有首位为0的,都是正数,三者相同,直接编译

//printf里面,%u会认为打印的无符号数,%d会任为是一个整形

//无符号数,每一位都是有效位,会被编译参与计算

int main()

{

int i = -20;

unsigned int j = 10;

printf("%d\n", i + j);

return 0;

}

36

//常见浮点数

//3.14159或者1E10(1.0*10^10)

int main()

{

int n = 9;//4byte

//float类型占用4字节,有7位有效数字

float* pfloat = (float*)&n;

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

printf("%f\n", *pfloat);//0.0000000(以浮点型的视角进行数的拿取,出问题了)

*pfloat = 9.0;//以浮点数的视角进行存放

printf("%d\n", n);//出问题

printf("%f\n", *pfloat);//9.0000000

return 0;

}

//根据IEEE754,任意一个二进制浮点型数字可以这么表示

//(-1)^s*M*2^E

//s为0时是正数,为1是是负数,M是有效数字,2^E指数(因为是二进制)

//单精度浮点数的存储模型1bit的s,8bit的E,23bit的M

// 双精度浮点数M是1bit的s,11bit的E,52bit的M

//十进制小数转化为二进制,小数部分乘以2,将得到得数字得整数部分放在二进制小数点后,直至十进制小数点后为0

//E可能是负数,但在定义中其为unsigned,所以我们需要加一个中间值进行存储,float类型加的是127,double类型加的是1023

int main()

{

float a = 5.5f;

//5.5二进制是101.1

//1.011*10^2

//s=0 M=1.011 E=2(原)

//s=0 M=011 E=2+127(事实上)

return 0;

}

37

//指着变量的大小 32位四个字节 64位8个字节

//数组名代表首元素的地址

int main()

{

//事实上,是把“hello bit”的首字母的地址存给了ps

    //常量字符串是没办法改的

const char* ps = "hello bit";

printf("%c\n", *ps);//打印出来的是h

char ch[] = "hello";

//打印出来一个字符串

printf("%s\n", ch);

return 0;

}

38

int main()

{

//指针数组

//数组里存放的指针

int a[5] = { 1,2,3,4,5 };

int b[5] = { 1,2,3,4,5 };

int c[5] = { 1,2,3,4,5 };

int* arr[] = { a,b,c };

//通过指针数组打印三个数组的数字

for (int i = 0; i <3; i++)

{

for (int j = 0; j < 5; j++)

{

printf("%d ", *(arr[i]) + j);

}

printf("\n");

}

return 0;

}

39

//数组指针

int main()

{

int arr[10] = { 1,2,3 };

int(*parr)[10] = &arr;//取出的是数组的地址

//parr就是一个数组指针——其中存放的是数组的地址

//arr——数组名是首元素的地址——arr[0]的地址

return 0;

}

40

int main()

{

int arr[10] = { 0 };

//这里如果&arr,取的是数组的地址,应该是一个数组指针

int* p1 = arr;

int(*parr)[10] = &arr;

//打印出来结果是一样的,但是意义不一样,一个是数组指针,一个是整形指针

printf("%p\n", arr);

printf("%p\n", &arr);

//还有一个分别,就是加一跳过的单位不一样

printf("%p\n", p1);

printf("%p\n", p1+1);

printf("%p\n", parr);

printf("%p\n", parr+1);

return 0;

}

41

//数组名代表首元素的地址

//但是有两个例外

//1.sizeof(数组名)——数组名代表整个数组,计算的是整个数组的大小,单位是字节

//2.&数组名,取用的是数组的地址

42

int main()

{

int arr[10] = { 0 };

int(*parr)[10] = &arr;

for (int i = 0; i < 10; i++)

{

//*parr指向的是数组里元素的地址,再进行解引用才能得到数组里面的元素

printf("%d", *(*(parr)+i));

}

return 0;

}

43

//这里,[5]需要加在后面因为arr代表的是拥有5个元素的数组的指针

void print2(int(*p)[5], int r, int k)

{

for (int i = 0; i < 3; i++)

{

for (int j = 0; j < 5; j++)

{

//这里加i就是加行的意思

//需要两次解引用才能访问到元素

printf("%d ", *(*(p + i) + j));

}

printf("\n");

}

}

int main()

{

int arr[3][5] = { {1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5 } };

print2(arr, 3, 5);//这里arr代表的是第一行的地址

return 0;

}

44

//辨认

//int arr[5]整形数组

//int *parr1[10]整形指针的数组,代表10个元素的指针

//int(*parr2)[10]数组指针,该指针指向这个数组,数组有10个元素,每个元素是int类型

//int(*parr3[10])[5]这是一个存储数组指针的数组,可以存储10个数组指针,每个数组有5个元素

45

//一维数组传参可以当成指针也可以当成数组

//这里arr[10] arr[10] *arr都可以

void test(int arr[])

{

;

}

//这里**arr也可以,把它当作地址的话,首元素就是指针,指针的地址就是二级指针

void test2(int* arr[20])

{

;

}

int main()

{

int arr[10] = { 0 };

int* arr2[10] = { 0 };

test(arr);

test2(arr2);

return 0;

}

46

//二维数组的传参

//只能省略行不能省略列

//也可以

//void test(int(*arr)[5]),因为传过去的就是一个一维数组的地址

//不可以(**arr),传过去的就不是一个二级指针

void test(int arr[][5])

{

;

}

int main()

{

int arr[3][5];

test(arr);

return 0;

}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值