Day 5
二维数组,int a[n][m],n行m列。下标从0到n-1(m-1)
数组快速赋相同值:
应用场景: 图形数据结构化为二维数组。
验证数组在内存里连续排列:
格式输出:%8d 和 %-8d ,一个是左边补齐空格,一个是右边补齐空格。
要处理10行10列的二维数组,当处理的数据大于10行10列时就会产生越界的现象。
最大的下标是9,10改成9就可以解决问题。
找数组里最大值:采用遍历法。设置一个专门的行列号,挨个比较,有更大的就更换下标。
打印完arr1,没有找到’\0’, 且arr1和arr2的首尾地址是相连的,arr2最后有’\0’,所以打印出了两个数组1内容。
不用for循环挨个打印字符数组,当用%s输出数组内容时,需要其最后含有‘\0’,或者内容需要是一个字符串。
编译器这方面不做检查,会出问题,这种小细节遗漏了是最讨厌的问题!
字符串最关键的是尾部的’\0’标志。还有输出时可用%s来输出。
注意:使用 gets() 时,系统会将最后“敲”的换行符从缓冲区中取出来,然后丢弃,所以缓冲区中不会遗留换行符。这就意味着,如果前面使用过 gets(),而后面又要从键盘给字符变量赋值的话就不需要吸收回车清空缓冲区了,因为缓冲区的回车已经被 gets() 取出来扔掉了。
用法:
gets(string);
strlen();求字符串的有效长度,算到\0(不含)结尾。
N = strlen(string);
有\的,是转义符,\t一起算作一位。
计算长度时,Sizeof算上了“\0”算的是占用空间大小,strlen不算\0。
惨痛经历,每次输出尽量写上“\n”,不然在终端找结果都没看到,以为没有输出。
字符串拷贝函数:strcpy();
strcpy(destination, source);
如果dest的空间不够,则超出的部分是不可控的。
strcat();字符串连接函数。
Strcat();只能用于两个字符串连接,如果非字符串连接,其结果是不可预料的。
数组取地址:整个数组不需要加&取地址符,但是里面的某一个元素取地址的话,需要加上&
Strcmp(str1, str2);字符串比较函数
逐位比较两字符串的ASCII码值。
一般不比大小,而是判断相等。如比较收到的信息和已有的是否相同。
还有strcasecmp();忽略大小写比较字符串。
Strncmp(p, p1, n);比较字符串的前n位。
strncpy(dest,src);复制src的前n位到dest内,且将dest里面原来的内容进行替换。
Strchr(); 功能为在一个串中查找给定字符的第一个匹配之处。函数原型为:char *strchr(const char *str, int c),即在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置
检查函数:
结果为布尔型。
大小写的转换:对字母进行操作。
toupper();
tolower();
总结:想到一个功能,先找找有没有现有的函数来实现,避免浪费时间造轮子。
Day 6
指针!无处不在
概述:指针的含义。
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type*var-name;在这里,type是指针的基类型,它必须是一个有效的C数据类型,var-name是指针变量的名称。用来声明指针的星号*与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
位,最小的单位。一个字节含8个位。一个字含4或8个字节。
变量和指针变量内存中的相对位置。
内存以字节为范围编号。
在C语言中,内存单元的地址称为指针,专门用来存放地址的变量,称为指针变量。
同一环境,任何类型的指针都占相同的字节。
三者等价。
指针的赋值:
两个指针变量指向同一个a,打印地址。等价于p = q;
确定的机器,内存和处理器间的地址线数量,决定指针占空间大小。
指针运算:地址量的运算
指针的关系运算:
数值定义但不赋值,内容是随机的。
空指针,不赋值,存的是0号内存单元。
指针+1,整型移动4个字节,double移动8个字节。
*p++
++优先级高,先和++运算,再取*
现在的开发,不是比谁的代码短,谁的代码难懂。而是看重单位时间产出,程序稳定性,可读性,团队沟通能力。
指针对数组不用加&,默认取第一位的地址。但是指针对里面的具体元素取地址要加&
陷阱:
P[1]是9不是6,前面已经p++了,p[i]实际上是*(p+i),不要被下标迷惑。
数组名,常量。指针,变量。只是在某些使用上有时候是通用的。
注意:指针变量和数组在访问数组中元素时,一定条件下其使用方法具有相同的形式,因为指针变量和数组名都是地址量。
但指针变量和数组的指针(或叫数组名)在本质上不同,指针变量是地址变量,而数组的指针是地址常量。
二维数组名是一个行指针,相当于二级指针。给其加星号*就降级了,移动一行改为移动一个数的位置。
Day 7
清楚字符串常量与数组的区别。
字符串常量的内存地址固定的,可以发现p1,p2,所指向的地址相同。
字符串常量不可修改!
下面的程序,把P指向的字符串接到ch的尾部。
分析:
第一个while遍历ch,找到尾部的\0,第二个while循环,以ch尾部的\0为起始点,将p指向的第一个字符“h”接到ch后,同时i++;
P++;继续往后面进行。
将所有的内容都接到ch,完成后,p已经指到了“hello world”最后的\0位置,此时puts(p),出来的是空白,所以可以先行用*q保存p的指向,然后输出q。
特点,i才是“标尺”,它的作用是一位一位的慢慢遍历,顺序连接两字符串。
指针数组:
赋值可以一步到位:
两种写法等价。*(p+1),也即p[1],就是基础的一级指针,再在行内来操作列的遍历。
思考题:指针数组名相当于什么样的指针?
相当于二级指针。一级指针,再取其地址,即此指针指向的也是一个指针。
二级指针,一级指针,被指变量之间的地址和内容
下面这种赋值更直接;
Void指针,使用时需要强制转换。而且在经过强制转换之前,不能进行任何运算。
Void指针的应用:库函数、系统调用等用的多,使得指针功能更加具有通用性。
Const使得变量常量化。
const放在int前面来修饰指针时,可以修改其存储的地址值,但是不能通过指针修改其指向的变量内容。
如果const放在后边,则与第一种相反。可以修改指向的变量值,不能修改存储的地址值,指向不能变。
即,const修饰的是谁,谁就不能修改。
而且只能在定义时就赋值。
如果有两个const,则啥也改不了,只能初始时定义好指向与被指变量的值。
Day 8
Main函数的参数说明:我们经常用的main函数都是不带参数的。因此main 后的括号都是空括号。实际上,main函数可以带参数,这个参数可以认为是 main函数的形式参数。C语言规定main函数的参数只能有两个, 习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为: main (argc,argv)C语言还规定argc(第一个形参)必须是整型变量,argv( 第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头应写为: main (argc,argv) int argv; char *argv[]; 或写成: main (int argc,char *argv[])
由于main函数不能被其它函数调用, 因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢? 实际上,main函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去
例如:网络程序,需要提供IP地址和端口,可以这样传参数。
Day 9
函数:(菜鸟定义)
函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
C 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
函数还有很多叫法,比如方法、子例程或程序,等等。
声明时,不可以缺少形参的类型。
先说明再调用,以免引起歧义。可以在最前端单独说明,后面main调用,实现放最后。这种风格的结构更易读。
引入头文件的作用:头文件里有函数声明,才能正常调用其内部先行声明的函数。
函数传参数:
函数外部的参数,可能和函数内的局部变量名称相同,但是,传参数的时候还是要按照流程进行。
或者,在main外定义出全局变量,同名变量可以在函数内直接调用。但是全局变量,所有函数均可使用修改,程序结果会受影响,并不建议使用。
复制传递,也叫值传递。
指针传递参数。
要注意交换赋值时加*取值.
将形参定义成指针,实参为字符串数组。在函数执行时就相当于指针指向这个字符串数组,进行遍历等操作。还可以加const到形参指针之前,使其可修改指向,不可修改指向的内容。
思考:复制传递和地址传递有何区别?
复制传递只是传递值,形参开辟新的空间,不能改变实参的值或内容。
地址传递可以改变实参,声明成const char *p,就改不了实参内容了。
数组传参;
也是全局变量,复制传参和地址传参。
注意,下面的求和函数例程。采用数组的复制传参,int data[] = a是错误的。传参的时候先行计算元素个数,也传递进函数。
函数实现:
S1是空格的情况下,s1往下遍历;
s1不是空格,s2不管是不是空格,都可以接收s1的值,因为s1一直超前或同时于s2。
指针函数:返回值是地址量,相当于一个指针。
面试笔试题,看一段代码,问你有什么问题。如果有指针,带星号,基本可以确定,说是内存有问题,执行结果不确定。
函数返回了一个局部变量的地址。
考察对内存管理是否敏感,对变量存储是否清楚。
修改:
定义时加入static,变为静态变量;
总结,指针函数返回值的3种情况:
全局变量的地址,静态变量static的地址,字符串常量的地址。
还有一种动态内存的(堆)地址,后面再涉及。
指针函数,删除字符串的空格。
上次的函数,是用函数直接编辑原串,再直接输出处理后的串。
而这次需要返回一个指针,指向处理完的字符串。
所以需要多定义一个* r, 来记录字符串的起始地址!
再以* s, * p来处理删除空格。
就能返回经过处理的字符串。
指针函数举例:
注意while的写法,直接用“\0”来卡位。还有定义一个r来存储数组的起始位置。
最简写法:
第一个while,根据优先级,先加加再判断,最后走到\0后边,减减一次。第二个while,循环判断的顺序,先赋值,再判断,再往下遍历连接。最后补齐\0
递归函数一般解决具有很强规律性的问题,注意递归出口。
函数指针:
?函数指用来存放函数的地址,这个地址是一个函数的入口地址
?函数名代表了函数的入口地址
?函数指针变量说明的一般形式如下:
<数据类型> ( *<函数指针名称>) (<参数说明列表>)
举例:声明,赋值,调用
用函数指针可以随意切换指向很多不同函数,调用可以不变,只改变赋值语句。
应用场景:把差异化的部分,变成通用的,用户自己去填写。
函数指针数组:即*p 变成 *p[] ,以数组内元素作为函数指针。
Day 10 知识补充
结构体与共用体
一、结构体struct
各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。
二、联合体union(共用体)
各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。不允许对联合体变量名U2直接赋值或其他操作。
typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写。
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
typedef 是由编译器执行解释的,#define语句是由预编译器进行处理的。