【鹏哥C语言初阶学习记录(已更新完成)】

一.基础知识
1.static修饰的变量是可以保存上一次修改的值,不会再次初始化;不加static的变量在循环时可能会再次初始化。具体的Static修饰局部变量的时候,局部变量出了作用域是不销毁的,其实本质上是改变了存储位置,此时不是存储在栈区而是在静态区,变量的生命周期变长,和程序的生命周期一样;static修饰全局变量的时候,全局变量的外部链接属性变成了内部链接属性,其他的源文件就不能再通过extern使用到全局变量了;static修饰函数情况与全局变量差不多。
2.除了自己单位的要求有提前宏定义的时候,最好在定义变量的时候初始化变量,不然就是随机值,这样写出来的结果会有错误。
3.大端存储和小端存储,首先需要知道地址的小地址和大地址,数据的低位和高位(不懂的可以补补基础知识),小端存储就是低位放小地址,高位放大地址;大端存储就是低位地址放高位数据,高位地址放低位数据,但是读取数据时都是从低地址开始读起的。
4.变量放置:
全局变量、静态变量都是放在静态区;
全局变量,静态变量不初始化的时候,默认会被初始化为0;
局部变量,放在栈区,不初始化的时候,默认值为随机值。
二、预处理命令
(1)#include命令,有<>和“”两种,<>代表只在系统路径下找,“”表示先在当前目录下找,找不到再在系统路径上找。
(2) #define是由预处理器来处理,这点与typedef不同(由编译阶段的编译器处理)。
typedef:如果放在所有函数之外,它的作用域就是从它定义开始直到文件尾;如果放在某个函数内,它的作用域就是从它定义开始直到该函数结尾。 #define无论在函数内外作用域都是定义处到文件结尾。
(3) #undef上文提到#define的作用域是从它声明开始到文件结尾,#undef就是取消之前的宏定义(也就是#define的标识符)
(4)#if命令要求判断条件为整型常量表达式,也就是说表达式中不能包含变量,而且结果必须是整数;而if后面的表达式没有限制,只要符合语法就行,这是#if和if的一个重要区别.
(5)#ifdef的作用是判断某个宏是否定义,如果该宏已经定义则执行后面的代码,在大型项目工程中经常用到。

三.指针
(1)定义
指针就是内存单元的一个编号,也就是地址,而地址也被称为指针。存放指针(地址)的变量就是指针变量。指针变量的大小取决于存放地址的一个空间,例如32位机器-32bit-4字节;64位机器-64bit-8字节;

int* P=&a;//int说明P指向的对象是int类型的,*说明p是指针变量
*P=20;//接引用操作符,意思是通过p中存放的地址,找到p所指向的对象

结构体指针变量->成员名
结构体对象.成员名
(2)指针类型
指针类型有什么用?
a.指针类型决定了指针在被解引用的时候访问几个字节。
按照下图可以类推double*,float*等。

int a=0x11223344int* P=&a;
*P=0;//这里对P进行解引用,是int*类型的指针,解引用访问4个字节
char* pa=(char*)&a;
*pa=0;//这里对pa进行解引用,是char*类型的指针,解引用访问1个字节

b.指针的类型决定了指针±1操作的时候,跳过几个字节,决定了指针的步长,这里的步长与解引用情况类似
比如int类型的解引用是4字节,那其步长也是4个字节。
问:那int
和float*是不是就可以通用呢?因为都是4个字节
答:不可以。如下图所示的指针,可以在地址中看到,这两个指针完全不一样,自己可以执行一下看看。

*pi=100;
*pf=100.0

(3)野指针
a.定义
野指针就是指针指向的位置是未知的(随机的,不正确的,没有明确限制的)
b.成因
1.指针未初始化
2.指针指向的空间释放了
3.指针越界了
c.如何规避野指针
1.指针初始化
2.小心指针越界
3.指针指向的空间释放即置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
(4)指针运算
vp++=0;可以看成vp=0;vp++;
a.指针-指针
得到的是指针和指针之间的元素的个数(PS:不是所有指针都能相减,指向同一块空间的两个指针才能相减)

int arr[10]={0};
printf("%d\n",&arr[0]-&arr[9]);//这个可以
char ch[5]={0};
printf("%d\n",&ch[0]-&arr[5]);//这个不可以

b.二级指针

int a=10;
int* pa=&a;//pa是指针变量,一级指针变量,*代表pa是指针,int是指pa指向的数据的类型是int类型
int** ppa=&pa;//ppa是一个二级指针变量
**ppa=20;

二级指针变量是用来存放一级指针变量的地址。
c.指针数组
定义:存放指针的数组就是指针数组

四.函数
(1)传值调用和传址调用
函数形参的改变不会影响实参,传址调用可以让函数内部和函数外部的变量建立起真正的联系,也就是让函数内部可以直接操作函数外部的变量。
什么时候函数需要传递地址:当需要对传递的参数自身进行操作,例如swap函数,需要交换ab的值,则需要传递地址;如果是add函数,之时需要ab的和返回,这种无需传递地址。
(2)递归的两个必要条件
a.存在限制条件,当满足这个条件限制时,递归便不再继续
b.每次递归调用后越来越接近这个限制条件。
(3)死递归或者栈溢出现象
栈溢出:程序有栈区,堆区和静态区;栈区一般存放局部变量,函数的形参(每一次函数的调用都会在栈区申请空间);所以递归调用函数的时候,如果深度太深,容易导致栈溢出。
如何解决:
a.将递归写成非递归
b.使用static对象替代非static局部对象,这样不仅可以减少调用和返回时的开销,而且static可保存递归调用的中间状态,并且可为各个调用层访问。

五.数组
(1)传参
数组传参时,形参有两种写法:数组和指针。地址是应该使用指针来接收。

int sz=sizeof(arr)/sizeof(arr[0]);//arr看似是数组,本质是指针变量

数组名本质上是:数组首元素的地址。但是有两个例外:
a.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节;
b.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

int arr[10];
printf("%p\n",arr);//首元素地址
printf("%p\n",&arr[0]);//首元素地址
printf("%p\n",&arr);//整个数组地址,但是由于整个数组的首地址是首元素地址,所以打印出来的一样

(2)二维数组
二维数组的数组名也表示数组首元素的地址。

int arr[3][4]={0};
printf("%d\n",sizeof(arr)/sizeof(arr[0]));//48(整个数组)/16(一列,表示第一行)
printf("%d\n",sizeof(arr[0])/sizeof(arr[0][0]));//16(一列,表示第一行)/4(第一行第一个元素)

六.逻辑运算符

+、-、&、|、!、^等,具体自己百度把,有个有意思的小题目
题目:不能创建临时变量,实现两个数的交换
提示:可以使用+(会有超出变量范围的限制),也可以使用^。。(有需要可以留言之后我再码出来代码)

七.结构体
(1)结构体是一些值的集合,这些值被称为结构变量,结构的每个成员可以是不同类型的变量。结构体的成员可以是标量、数组、指针甚至是其他结构体。
(2)结构体的声明

struct tag{
member-list;
}variable_list;//此处这个变量可省略,可之后再申明结构体变量;
/*tag是结构体类型,menber是成员变量,variable_list是结构体变量,其是全局变量*/

(3)结构体初始化

/*假设之前我已经对结构体Peo类型进行了定义*/
struct Peo p1={“zhangsan”,18511601018,“男”,181};//结构体变量的创建
//可以不完全初始化,比如只初始化张三,后面不初始化

(4)结构体成员的访问
利用->和.符号。
(5)结构体传参
结构体传参的时候,尽量选择传地址,因为如果是直接传p1对象,那么如果p1比较大,传去函数的比较大,函数就需要开辟更大的空间,导致性能下降。

void print1(struct Peo*sp)//假设第一个函数
void print2(struct Peo p)//假设第二个函数
print1(p1);//假设p1是个结构体,调用p1
Print2(&p1)

八、作业精选分析
(1)运算符

int main()
{int a,b,c;
a=5;
c=++a;//此时a=6,c=6
b=++c,c++,++a,a++;//第一个逗号执行完,c=7,b=7;第二个执行完c=8;第三个执行完,a=7;第四个a=8,b=8
b+=a++ + c;//+=优先级比较低,先a++后=8,8+8=16;再与b加,16+7=23,
//最终应该是a=9,b=23,c=8
}

(2)写一个函数返回参数二进制中1的个数
思路:除了常规思路(例如将数字除2取余),推荐一种算法n=n&(n-1),例如n=15(1111),则n-1=14(1110);n&(n-1)=1110,每次循环与一次就消去一位1;

int count_num_of(int n)
{
int count=0;
while(n)
{
n=n&(n-1);//
count++;
}
return count;
}

类似同样的操作是看一个数是不是2的n次方。

2^1=10(二进制)
2^2=100;
2^3=1000;
if(n&(n-1)==0)
{
}

类似同样的操作是看两个数中不同位数有多少个。

int count_diff_bit(int m,int n)
{
int count=0;
int ret=m^n;//异或操作符,相同为0相异为1
while(ret)//统计一下ret的二进制中有几个1,有1则进入循环
{
ret=ret &(ret-1);
count++;
}
return count;
}

(4)针对输入的数字,输出用*组成的X形图案(引申,如果是这种输出图案的题都不难,仔细观察找出他们的规律)
思路:观察规律可知,在第一个对角线上的✳号是当I=J时可成立;第二条对角线是当i+j=n-1时,可打印

int main()
{
	int n=0;
	while(scanf("%d,&n")==1)
	{
		int i=0;
		int j=0;
		for(i=0;i<n;i++)
		{
			for(j=0;j<n;j++)
			{
				if(i=j)
				{
					printf("*");
				}
				else if(i+j==(n-1))
				{
					printf("*");
				}
				else
				{
					printf("\n");
				}
			}
		}
	}

}
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值