(一)C程序设计语言

chap1 导言

 

  •     linux:  cc main.c; make main.c;  gcc main.c 生成a.out;-o 可以指定生成的文件名称。
  • windows vs2015文本格式为:『带BOM的utf8』 ;uft8是linux可以识别的格式。
  • windows vs2015文本格式为:『ANSI』

 

 符号常量的使用

 

  • windows下EOF:Ctrl+Z;
  • linux下EOF:Ctrl+D  

 

  • 后面自己都忘了,是啥写法。简洁、易懂,易维护为主。 
  • getchar只有看到回车,才下发。行缓冲。只遇到回车时,才一次getchar.
  • OutputDebugStringA是可以在控制台输出一些信息,不需要使用QDebug就可以在控制台输出信息的。

 看见一个/n就认为是换行。

这样在编译阶段就会报错。写成:'\n' = c,写错了,就会报错;判断语句

 

 这个wrod_out和word_in的使用,很神奇。

 

 

  

  •  exe执行,然后echo %ERRORLEVEL% 就能获得main函数的这个返回值。

  • Linux下main的返回值为:-1;255;所以其值就是一个8bit的数。-1 == 255

    • 写批处理程序的时候,有一个值。

 3e41ebcc2eabf45f473fcb85fd31326c.png

​​​​​​

  • C语言只有传值调用。

    • 函数里面,无法改变参数的值的。

    • s的值(指针地址)没有发生变化,只是s指向的内容发生了变化。

 

 是否将数组后面初始化为0,是编译器的行为。要具体看编译器如何处理这个问题。

1-16 : 把break去掉就可以了。让他一直在那里循环就可以了。加了限制,可以让他提前退出。

1-17:我靠,这里竟然用了strcopy函数。完全是一个活生生的案例啊。太妙了。

1-18:

1-19:reverse()

  • 如何解决这个问题呢?
    • 把这个变量定义为全局变量。

chap2 数据类型及长度 

 

 

这里面全是基础应用;比如和Visual Studio的风格控制,格式控制代码,都有用这样的思路。

  • string.h

  • 在linux下没有strcpy_s函数;所以需要使用『pragma warning(disable:4996)』把错误屏蔽掉。

 

  • limits中有各个数据类型的最大最小的表示范围。 

140def9d1c0f8b34a6f74e4d82b1c390.png

char

short

int 

long

long long

float 

double

long double

1

2

4

4

8

4

8

8

数据类型和编译器有关。linux下long long是8个bit的。

  • linux下面 long double占16个字节。

  • double和float的limits在 float.h 头文件中。

找FLT_MAX在哪个文件里面?正则表达式。

gcc编译器里面的float.h

2.3 常量 

常量表达式,在编译时计算。

宏定义;

字符串常量不可以修改。修改是失败的。字符串常量,是放在一个只读区域的!!!存在exe文件中。

enum常量;最大值就是Unsigned INT,占4个字节。

都不做重复性检查的。

 2.4 声明

  • 默认情况下,外部变量、静态变量,初始化为0。但全局变量、静态变量,只进行一次初始化。
  • 自动变量 == 局部变量;每次都执行一次初始化。
  • 2.4.1 全局变量 静态变量的初始化
    • ① 如果初始化为0, 编译后的大小不会增加;证明,初始化为0, 编译器采用了简单的方法标志为初始化为0.
    • ② 如果初始化为其他值,编译后的大小会增加,exe。初始化的内容直接放在exe里面。
    • ③ 不使用/引用/调用的静态变量,可能会被编译器优化掉,具体看编译器的实现。
  • 总结:C语言中,全局变量/静态比那里在程序执行前已经初始化。

 

 

  •  关于优先级:直接加括号,不用记忆这些。不要为难自己。就算写出来了,但是别人看也看不懂,看起来会很老火。所以易读易用,容易理解比较好。
  • 不建议写在一起。代码的需求:①自己能看明白;②别人能看明白。

  

 2.7 类型转换

  •  有符号 无符号的转换,在内存中不会有什么变化,其16进制的值不会有什么变化。只是解释器发生了变化。
  • 低级到高级: char转为short,高位填0;-1, 高位填1;
  • 高级到低级:扔掉高级部分。
    • visual studio中,int向下转到Char/short,并不会出现编译告警;
    • 有符号、无符号,作比较时,自动转为无符号数这种地方容易出bug;一个有符号的 signed -1 > unsigned 0;  
  • 浮点数的转化:int转float可能会丢失信息。
    • 自己做四舍五入:float x1= 5.9f;  int  v1 = (int)(x1 + 0.5); 加个0.5可以自动四舍五入。

 

 这里int转为Float后,后面的小数点会丢失。精度丢失;所以一个很大的int数,转为float的时候,整数部分会丢失。

int转float的坑_qq_45740393的博客-CSDN博客_int转float

 

 

也就是丢掉了末尾的7位,对于一个连续数字而言:每隔127个数字,都会是一个精度阶梯;float丢掉了最后的7位。导致了精度误差。所以对于很大的整型数据,转换为Float会有精度损失,造成意想不到的bug。 

 在这里插入图片描述

参考如下代码:(进行了一些修改) 

 C语言打印数据的二进制格式-原理解析与编程实现_码农爱学习的博客-CSDN博客_c语言打印二进制

/* C程序设计语言 */
#include <stdio.h>
#include <string.h>


//#define NUMSTART 2147483647
//#define NUMSTART 112234567
//#define NUMSTART 111111111	/ 8		// 8位没问题: 111111111 9位1, 6个或7个;末尾三位。 // 也就是前24位的精度没有问题。


#define NUMSTART 16777219		// 理论上16777215之前的精度没问题;1677W


void Mysqueeze(char s[], char c);			// 从字符串s中删除所有的字母c
void Mysqueeze2(char s1[], char s2[]);
void testInt2Float();
void enter(float d, int i);
void printLineNumber(int a);
void printBin(int a, int tmp);


int main(int argc, char* argv[])
{
	// 
	testInt2Float();


	return 0;
}
// 从字符串s中删除所有的字母c
void Mysqueeze(char s[], char c)
{
	int i, j;
	for (i = 0, j = 0; s[i] != '\0'; i++)
	{
		if (s[i] != c)
			s[j++] = s[i];
	}
	s[j] = '\0';
}


// 字符串拼接:t接到s的末尾,假设s中有足够的空间保存 s + t
void Mystrcat(char s[], char t[])
{
	int i = 0, j = 0;
	while (s[i] != '\0')
		i++;
	while ((s[i++] = t[j++]) != '\0')
		;
}


// s1中任意与s2匹配的字符都删除
void Mysqueeze2(char s1[], char s2[])
{
	return;
}


/* 反转字符串 */
void reverse(char s[])
{
	// 几次遍历?
	/* 1. 生成一个同等长度的,t,然后遍历赋值;2. 两次遍历;2N---->N+N/2=1.5N;
	*/


	// 卧槽,想都没想到,还能折半查找写这个代码!!! 直接进行原地交换。我晕。
	// 无论奇偶,只用交换两边即可。
	int i = 0, len = 0;
	while (s[i])
	{
		i++;
	}
	len = i;
	for (int j = 0; j < len / 2; j++)
	{
		char tmp = s[j];
		s[j] = s[len - 1 - j];
		s[len -1 - j] = tmp;
	}


}




static int a = NUMSTART;		// int_max
static int b = NUMSTART;
static int print = 1;		// 换行控制变量
static linenumber = 0;		// 行号
static int tmp = 1;


/* 测试int2float的精度变化问题 */
void testInt2Float()
{
	for (int i = 0; i < 500; i++)
	{
		float fb = (float)a;		// a是int类型,每次-1;但强制转换为float类型
		a = a - 1;
		enter(fb, i);
	}




}


void enter(float d, int i)
{
	// 判断浮点数是否更新,更新就换行
	if (d != b) {
		printLineNumber(linenumber);	// 输出行号
		printf("%d  |  \n", b);			// d是float表示的数, a是int实际的数; 隔多少个数,b == a?
		printBin(b, tmp);
		printf("\n");
		// 变量重置
		print = 0;
		linenumber = 0;
		tmp++;
		b = (int)d;						// float转int精度不够  int->float->int, 左边int-1,右边int还是同一个值
		// a-1 -> d=float(a-1) -> b = int(d) float表示的int值;int实际在-1, 但float没发现。
	}
	else{
		printLineNumber(linenumber);
		printf("%d  |  ", b);		// 打印每次的浮点数


		linenumber++;
	}


	if (print % 16 == 0)
	{
		print++;
		printf("\n");
	}
	else {
		print++;
	}
}
void printLineNumber(int a)
{
	if (a < 10)
	{
		printf("%d   ", a);
	}
	else if (a > 100)
	{
		printf("%d ", a);
	}
	else {
		printf("%d  ", a);
	}
}
void printBin(int a, int tmp)
{
	char s[32];
	_itoa(a, s, 2);


	printf("%d ->", a);
	// 在字符串倒数第7个位置,加入别的字符;
	for (int i = 0, j = 0; i < 34; i++)
	{
		if (i == 24)
		{
			printf("-");
		}
		else if (i == 25)
		{
			printf(">");
		}
		else if(s[j] != '\0')
			printf("%c", s[j++]);


	}
}

  • float 不会自动转换为double;float主要是为了在使用较大的数组时节省存储空间,有时也为了计生机器执行时间。
  • double双精度算术运算特别费时。
  • linux下查找函数在哪里:man isdigit ; man atoi

  

2.8 自增自减运算符

  •  从汇编语言:调试——>窗口->反汇编,看看汇编代码是怎么进行的。

  •  数组越界了:数组访问了不该访问的东西。这个程序的运行完全看人品。
    • 如何解决这个问题?——增大空间。

 

 可以认真去阅读《微软的标准库代码》:里面有很多思想,都可以借鉴。   

UCRT下载地址:https://github.com/huangqinjin/ucrt

2.9 按位运算符

 按位运算符,是最简单的加密算法?

  • 6个按位运算符只能用于整型操作数:short,int,long,long long类型。
  • 与、或、非、异或、左移、右移; 异或XOR ^
  • 用异或用于加密和解密
    • 同一个数异或两次,会等于原来的值。(X^F)^F=X, (X|F)&F=F
  • 取非的时候, 与类型有关的。x = x & ~077, 与操作数的数值类型有关系!

  • unsigned x = -1;默认的是Int;  static x = 0;  也都默认的是int类型的。什么都不写的时候,都是int类型,int的简写。
  •  getbits函数有什么用途?
    • 所以用~0的用途,就在与此,希望~0可以自动分配长度;不能用一个统一的长度来计算。

 

  •  有点像Ctrl+F的那个替换功能?
    • 2-6思路:先清零,再相加(加法,就是求或就可以了;不进位的加法,或就可以了)?

习题2-7:

 习题2-8:循环右移

 先取出来x1,把x1放到最左边;x右移,因为是无符号的,所以只会补0;然后x1|y = x1+y 

移位的速度是很快的; m>>1 == m/2; 移位用得好的话,代码的速度会大大提升。

2.10 赋值运算符与表达式  条件表达式  预算符优先级与求值次序

  • x是传值,是局部变量,所以是赋值。可以直接对x进行操作,而不会改变原来的值。

  •  对bitcount加速:删除最后一个二进制位的做法如下:
  • 加速分析:原来是需要移位n位;现在4只有1个1,只需要一次计算。有多少个1,就执行多少次!而之前是,有多少位,就得移动多少次;4的话,得移动2次。所以加速了。
    • 都是111的情况下,都要执行3次计算;但是 1100的情况下,移位需要计算4次;而2-9只需要执行2次即可,减少了计算量。
    • 主要原因是:100 & 011   x=x&(x-1)的作用太好了,直接消除了1个1的位置,减少了很多计算。直接把1的那个位置全部清零了。所以快了
    • 据说与比移位快。
    • 看循环次数,就明显占优。所以优化的地方,有可能是:减少冗余的循环次数。

 

  •  大厂的代码规则是很严格的;if后面必须加括号;否则后面加代码的话,没有括号,直接把整个代码干废了。
  • 条件表达式的返回值的类型:是跟f,n的类型有关系,所以返回值是float类型,不希望这个表达式结束之后丢失精度。

 

求值顺序对结果会产生副作用,因此尽量少写这种依赖项强的代码!——————不知道典型的哪些场景会发生此类事情?

 

 3. 控制流

 C语言的缩进是给自己看的;编译器只看括号的。这个虽然缩进了,看起来是一串的,但是编译器是看不见的。

  • 高级环境的代码提示功能,可以让你尽量减少错误。高手用记事本的说法,已经过去了。还是高效便捷、少出错为主。

 

 二分查找法:要求数组是排序过的。

  • 现在的计算机太快了,没时间。就让他多执行一些次数,1000次,还是0。 100W次,代码1 32ms;代码2:46ms。
  • 1000W次,代码1:422 ms;代码2:453ms
  • array size:编译器对代码也会进行优化。肯定不是这里的问题,这都不算优化。关键的优化地方还是在循环次数那里的。

 

按着alt键进行编辑,就是列编辑模式,一下子就可以写上这么多。 

 

 

  • 这个版本可以跳过空白符;做一些基础的异常处理。

 

 linux下查文件:man isspace

 

  •  代码虽然不多,但是讲清楚,还是很难的。

 

 

只做最后一遍排序,也可以实现排序的功能:

gap=1,就能确保能排序好。

 

  •  为什么需要 gap/2;跟交换顺序有关系,shell前面的步骤可以减少交换的步骤。所以,排序算法前面是进行粗排,减少了交换的次数。
  • 可以用一个变量来统计交换的次数。所以算法加速的很多关键点,都是减少其中的一些循环、访问等。
  • 都是减少其中时间消耗过多的模块

 

 

 a-d 替换为abcd

 

 

 3.6 do-while循环

  • 实际中用的很少。

3-4:不能处理INT_MIN,  超出其表示范围了。

微软如何处理这个函数的呢?crt->itoa。 radix是转换的进制。

  • 标准的库函数:

  •  有兴趣去看看星仔的源码怎么写的,这里。

 

 itob看xtoa的代码,照着改,就可以了。

  •  多度微软源代码,大师级别的代码,对自己会有很大的帮助的。

3.10

trim这个函数平时用的还是比较多的。

 

  •  goto只能向后跳。
  • 靠一个goto和标号,程序很稳定。
  • 严格的使用规则,只能向后跳,不能向前跳。
  • goto也有它有用的时候。

可以直接生产的函数:①最前面进行参数有效性的检查;

// 可生产的函数示例:函数头有return; 函数主题中没有return;函数末尾有return


/*
	返回:1-成功; 0-失败
*/
int printFile(const char* pFilePath)
{
	int nResult = 0;
	FILE* stream = NULL;			// 读取文件内容
	long lFileSize = 0;
	char *pFileContent = NULL;
	size_t nReaded = 0;


	if (NULL == pFilePath)			// 路径为空  参数有效性检查
	{
		return 0;
	}
	if (pFilePath[0] == '\0')
	{
		return 0;
	}
	 
	stream = fopen(pFilePath, 'rb');// 打开文件
	if (NULL == stream)				// 打开文件失败
	{
		goto FINISH;
	}


	fseek(stream, 0, SEEK_END);
	lFileSize = ftell(stream);		// 获取文件长度,判断文件大小是否有效
	fseek(stream, 0, SEEK_SET);


	if (lFileSize == -1)			// 如果文件长度有问题,ftell返回-1
	{
		goto FINISH;
	}


	pFileContent = (char *)malloc(lFileSize + 1);
	if (NULL == pFileContent)		// 内存分配失败
	{
		goto FINISH;
	}


	nReaded = fread(pFileContent, sizeof(char), lFileSize, stream);	// fread的返回值为size_t 
	if ((size_t)lFileSize != nReaded)
	{
		goto  FINISH;
	}


	pFileContent[lFileSize] = '\0';
	printf("%s", pFileContent);


	nResult = 1;


FINISH:								// 统一做所有的释放资源:这是一串复用的代码;做返回值的检查
	if (NULL != stream)
	{
		fclose(stream);
		stream = NULL;
	}
	if (NULL != pFileContent)
	{
		free(pFileContent);
		pFileContent = NULL;
	}
	return  nResult ;
}

chap4 函数

  • 不是所有的路径都有返回值。
    •  grep函数的特例

 

 

 

  •  从右到左去找。

4.2 返回非整型的函数

  •  0.0是double类型;0.0f是double类型。

 

编译器从上往下看函数,需要先声明,再使用。

  • 这就是编译器看到不一样的;所以就会warning和error

 4.2 外部变量

  •  技术含量较高的一门课;堆栈比较难懂。
  • 逆波兰表达式。

 

 

 

4.4 函数本质(堆栈的视角)

几个子程序插入,靠堆栈实现。

子函数的调用,就是靠堆栈来实现的。

 

 

 

  •  堆栈,是程序运行过程中就有的一块存储区域。
    • ① 

  • 函数调用,传参。函数是如何取到参数的????
    • 调用时,将这个参数push压入堆栈;然后被调用函数去堆栈取这个参数。
  • 函数传参数,是通过堆栈实现的。

 

  • 递归为什么会堆栈溢出呢?
    • 堆栈一直在压入压入压入
    • 一直调用子函数,相当于函数都在调用自己,所以一直在压入堆栈。调用一次子函数,就要使用一次堆栈。
    • ① 递归的效率并不是很高;② 递归多了会让堆栈溢出
  • 变参函数
    • 参数个数不确定,就是变参。
    • 通过前面的%来判断有几个参数。

  • 32位CPU,一个堆栈的格子是4个字节;4 bytes;
  • printf("%d")————执行者会从堆栈中去取一个数,所以dirty read,读了不该读的东西;eax去堆栈找了一个数。
  • VS的默认堆栈大小是多大:default stack size, 默认是1M。
    • 可以实际测试一下这个堆栈到底多大;char s【1024*1024],理论上是会崩溃的。确实崩溃了。
    • 如果有很大的变量,不要做成堆栈变量。
    • 把较大的变量放在堆栈外面。
    • malloc free
      • malloc申请的内存在Heap上,不在stack上。heap没有stack快。
      • stack的速度很快。

 


4.3 外部变量

 4.4 作用域规则

  •  外部变量 == 全局变量  使用:extern int g_x;
  • static 静态变量
  • 自动变量==堆栈变量;局部变量。
  • 函数的形参:寄存器变量。

  •  无论寄存器变量是否放在寄存器中,它的地址都是不可访问的。内存才是有地址的,寄存器是没有地址的。寄存器速度是比较快的。
  • 不需要加寄存器变量。因为编译器可以轻松就给你优化掉,所以不用在意这个。

  • static函数
  • 定义:告诉编译器要预留多少空间。
  • 声明:不可以对变量初始化;

  •  编译:C全部变成obj;
  • 链接:obj生成exe

 

  •  static变量,static函数,只能作用于当前的文件。
  • 其他的变量都是右作用域的。

  •   静态变量的内存分配问题

  •  局部变量,不初始化,就是随机值。
  • 全局变量,会自动初始化为0。

4.9 递归

  • 求阶乘,4800次,在14的时候,堆栈就溢出了。
  • 只要函数调用次数足够多,一定也会导致堆栈溢出。

 qsort 去画图,一步步的理解里面的逻辑是什么!才能学得比较明白的。qsort快速排序算法

  • 详细分析是怎么运行的qsort代码

4.11 C预处理器 

  • 可以看到宏展开的样子,test2.i这个.i文件,就是展开了宏的样子。
  • exe
    • 预处理每一个文件
    • 编译每一个文件,形成中间文件obj
    • 链接所有的obj文件,形成exe文件。

 

 宏定义的时候,必须加括号。可以看看具体的预处理之后的文件,看看宏替换的部分,是否有歧义。

  • #define 进行宏定义
  • #undef 可以取消宏定义
  • 条件包含
    • #if
    • #endif

  •  头文件只想包含一次
    • #ifndef __TEST3H__
    • #define __TEST3H__
    • 可以用来只包含一次。只要两个宏是一样的就可以。
  • 现在的编译体系:pragma once跟上面的define是一样的。而且不用担心宏不唯一。
    • 现在的编译器都支持pragma once了。
  • 附加包含目录,在目录下没找到,就去附加包含目录下去找。
  • 用VC去管理文件。组织文件。新建筛选器;代码下面要加包含目录。

chap5 指针与数组

  •  内存的本质是什么?
    • 32位能寻址4G的内存空间。
    • 32位,8个F。
    • 内存就是一个线性表格;CPU可以根据地址读取该地址对应的内容,也可以写入。
  • 没有识别的内存用于管理外设等需求。如网卡、声卡等内容
  • 4G的内存,有保留一部分自己来用的。

 

  • 每个程序都以为自己又一个内存在用。
  • 操作系统会将虚拟地址转化为实际地址。

 

  •  链接成exe了,就没有名字了。 int 的变量名,都变成了内存中的值了。
    • 编译器预留多少个空间;
    • 执行的时候,堆栈没那么大,所以一执行的时候就崩溃了。
  • 8个f,来表示一个地址;4 byte;
    • 一个地址是32位;4个字节;
    • 指针的sizeof大小,由它能访问的内存空间大小决定。
    •  指针的sizeof都是一样的。跟指针指向的类型是没有关系的,就是指向了一个地址,地址的大小都是一样的

  • 预留4个字节的空间给指针。
  • 指针的指针,二级指针:
    • p,q,m都是指针,都是一样的编译器预留一样的空间。

  •  p的指针的内容是a的地址;
  • (void *)类型的指针;不可以进行++,--;赋值都是可以的。

5.2 指针与函数参数

  • 传值方式:传参。
    • 在堆栈中,申请了参数的拷贝数据;形参,实参,都是在堆栈中申请的堆栈变量。交换了拷贝的ab的值,但是原来的ab没有变化。
    • 只是交换了压入了堆栈的参数的值;
  • 这就是C语言的传值调用:传值,是在堆栈中进行的。
  • 为什么改为* swap就工作了呢? 因为传入了实参的地址。

  •  还是传值调用,但是传递的是地址。
    • 使用地址的swap,就是正确的swap了。

  •  无论传递的啥,都是一份拷贝。在堆栈区中的一堆值。
    • 只是里面有地址,可以使用这些地址。
    • 操作的是:*px, *py;
  • C语言是以传值的方式完成函数调用。

5.2 指针与函数参数

  •  数组标号是一个常量;

  •  
  • padding, 32位系统,4字节对齐;64位系统,8字节对齐。
  • 网络传输的时候,可以不Padding;
  • 指针类型,在跳指针的的时候,决定了跳多少大小。

 

  •  amsg在堆栈变量中;pmsg指向的地址,不在堆栈中,虽然这个变量在堆栈中,但是其指向的是一个字符串常量,所以指向的内容不在堆栈内存中。
    • 字符串常量是全局有效的。
  • 堆栈的内存是在反复使用,局部变量字符串数组,就在调用函数的堆栈中。局部变量字符串数组是不可以的,是垃圾信息;从调用函数堆栈出来之后,就变化了。
  • 字符串常量的地址,在程序的终身都是有效的。
  • strlen计算字符串的长度
    • 使用指针来计算字符串的长度;
    • s[i]与*(s+i)的效率,谁最高?

  • strcpy 字符串拷贝

05_0500 5.5 字符指针与函数

char amsg[]与char*pmsg的区别
1.amsg指向的地址位于堆栈之内
2.pmsg指向的地址位于堆栈之外

  • 它指向的字符串常量是全局有效的;

3.字符串常量可读,是否可写是未定义的
4.字符串常量可用于函数返回值,但局部变量字符串数组就不可以了(事实证明)

  • 堆栈平衡:就是从调用函数foo返回到Main函数的堆栈;
  • pRet在堆栈退出后,就变成了垃圾信息;虽然内存的数据没有被擦除,但是已经变成了垃圾信息;
  • foo_ebp这个地址又变成了voo的栈;所以栈是在重复使用的;
  • 由于返回的是字符串常量;虽然pRet的返回值是在foo函数堆栈中;但是
  • https://www.jianshu.com/p/21b5b720fb75
    •  所有的字符窜常量都被放在静态内存区
      因为字符串常量很少需要修改,放在静态内存区会提高效率

以下一个函数来举例说明:

int add(int x,int y)

{

      int z=x+y;

      return z;

}

int main()

{

       int sum=add(3,4);

       return 0;

}

该函数返回部分的汇编代码:

return z;

001813F7 8B 45 F8             mov         eax,dword ptr [z] 

    return这一段中,可以看到z中计算好的数据移到eax寄存器中,由eax寄存器将值带给调用该函数的函数变量。

在返回这些类型时,系统将该函数所要返回的值移到寄存器中,栈顶指针下移,栈中的局部变量都死亡,寄存器中的数据再返回给调用该函数的函数所要接收的变量。
————————————————
版权声明:本文为CSDN博主「Learning_zhang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接: https://blog.csdn.net/Learning_zhang/article/details/52389035

 

  •  字符串长度

s与*(s+i)的效率之差

 
*s++与*--p的意思
用执行结果来查看其意思,如果考试需要,请记住

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=43

 


  •  strcpy
    • // 注意:括号内有复制;
    • 可以假设和思考一下,复制的整个过程是如何进行的;
  • strncpy 只复制一部分;万一空间不够呢?

 

  •  strcat;
  • strncat;
  • strcmp; 比较两个字符串
  • strncmp;之比较前几个;
  • strend(s,t) t是否存在s的尾部

不建议一条写两条语句,最好容易理解,一条就写一条;

习题答案见:krx50500.c

05_0600 5.6 指针数组以及指向指针的指针

指针是变量
指针在计算机的内存里,地址在人的脑海里.

何为指针: 指针是变量,使用的时候会加载到内存里,可以对其求地址
何为地址: 地址是客观存在的,学习C语言,脑海中要有地址的概念

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=44

回到目录

 文本行;排序;输出;

#include <stdio.h>
#include <string.h>

#define MAXLINES 5000    /*进行排序的最大文本行数*/
char *lineptr[MAXLINES]; /*指向文本行的指针数组*/

#define MAXLEN 1000      /*每个输入文本行的最大长度*/

#define ALLOCSIZE 10000  /*可用空间大小*/
static char allocbuf[ALLOCSIZE]; /*alloc使用的存储区*/
static char *allocp = allocbuf;  /*下一个空闲位置*/


char *alloc(int n);
int getline(char s[], int lim);
int readlines(char *lineptr[],  int nlines);
void writelines(char *lineptr[], int nlines);
void swap(char *v[], int i, int j);
void qsort(char *lineptr[], int left, int right);



/*对输入的文本行进行排序*/
int main()
{
    int nlines;  /*读取的输入行数目*/

    if ((nlines = readlines(lineptr, MAXLINES)) >= 0)
    {
        qsort(lineptr, 0, nlines - 1);
        writelines(lineptr, nlines);
        return 0;
    }
    else
    {
        printf("error: input too big to sort\n");
        return 1;
    }
}


/*返回指向n个字符的指针*/
char *alloc(int n)
{
    /*有足够的空闲空间*/
    if (allocbuf + ALLOCSIZE - allocp >= n)
    {
        allocp += n;
        return allocp - n;/*返回分配前的指针p*/
    }
    else /*空闲空间不够*/
    {
        return 0;
    }
}


/*getline函数: 将一行读入到s中并返回其长度*/
int getline(char s[], int lim)
{
    int c, i;

    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
    {
        s[i] = c;
    }

    if (c == '\n')
    {
        s[i] = c;
        ++i;
    }
    s[i] = '\0';

    return i;
}


/*读取输入行*/
int readlines(char *lineptr[], int maxlines)
{
    int len, nlines;
    char *p, line[MAXLEN];
    nlines = 0;

    while ((len = getline(line, MAXLEN)) > 0)
    {
        if (nlines >= maxlines || (p = alloc(len)) == NULL)
        {
            return -1;
        }
        else
        {
            line[len-1] = '\0';/*删除换行符*/
            strcpy(p, line);
            lineptr[nlines++] = p;
        }
    }

    return nlines;
}


/*写输出行*/
void writelines(char *lineptr[], int nlines)
{
    while (nlines-- > 0)
        printf("%s\n", *lineptr++);
}


void swap(char *v[], int i, int j)
{
    char *temp;

    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}


/*按递增顺序对v[left] ... v[right]进行排序*/
void qsort(char *v[], int left, int right)
{
    int i, last;

    if (left >= right)
        return ;
    swap(v, left, (left+right)/2);
    last = left;
    for (i = left+1;i <= right; i++)
        if (strcmp(v[i], v[left]) < 0)
            swap(v, ++last, i);

    swap(v, left, last);
    qsort(v, left, last-1);
    qsort(v, last+1, right);
}


5-7 重写函数readlines,将输入的文本行存储到由main函数提供的一个数组中,而不是存储到调用alloc分配的存储空间中。该函数的运行速度比改写前快多少?

readlines将读取的输入行保存在主函数提供的数组 `linestor` 中。字符指针 `p` 的初值指向 `linestor` 的第一个元素。

这个版本的readlines函数因为不去频繁申请新的存储空间,速度会比原始版本稍快。

  • 但在现在CPU的速度,已经不比之前快多少了;
int readlines(char *lineptr[], char *linestor, int maxlines){
    int len, nlines;
    char line[MAXLEN];
    char *p = linestor;
    char *linestop = linestor + MAXSTOR;
    nlines = 0;
    while((len = getline(line, MAXLEN)) > 0){
        if(nlines >= maxlines || p + len > linestop)
            return -1;
        else{
            line[len - 1] = '\0'; // 删除换行符
            strcpy(p, line);
            lineptr[nlines++] = p;
            p += len;
        }
    }
    return nlines;
}


/*读取输入行*/
int readlines(char *lineptr[], int maxlines)
{
    int len, nlines;
    char *p, line[MAXLEN];
    nlines = 0;

    while ((len = getline(line, MAXLEN)) > 0)
    {
        if (nlines >= maxlines || (p = alloc(len)) == NULL)
        {
            return -1;
        }
        else
        {
            line[len-1] = '\0';/*删除换行符*/
            strcpy(p, line);
            lineptr[nlines++] = p;
        }
    }

    return nlines;
}

第二个有alloc,稍微慢一点;

05_0700 5.7 多维数组

多维数组在内存中的表现形式
定义数组的方法 变量名[行][列]
char arr[2][3] = {'a','b','c','1','2','3'}; //2表示行,3表示列

多维数组作为函数参数
f(int daytab[2][13]) //OK 虽然参数传的是数组,但是已经退化为指针了;反汇编push的只有一个数组首地址;
f(int daytab[][13]) //OK  列的信息是不可少的;13在数组内容索引中是有帮助的;首地址为基准,列多少,来访问内容;
f(int (*daytab)[13]) //OK  用得少;
int *daytab[13] //ERROR

代码:https://q1024.com/p/item.php?u=krc
视频:http://www.bilibili.com/video/BV12L4y1Y76R?p=45

回到目录

  • 二维数组一定要知道列是多少个;它可以根据内存来计算行多少;这样指针跳的时候才知道跳到哪里。
  • 数组不越界就可以了;一般只进行一般的检查;如果需要检查的很多的话,还不如调用者自己检查;
    • 检查参数可以做的非常详细;有时候检查的参数量就非常大;
    • 可以保证:基本不崩溃;是基本的要求

  •  二位数组用的很少;因为可以用一维数组来替换;
  • 图片,都是一个线性数组;自己控制行和列;
  • 可以定义为一个结构体;二维数组可以扁平化;
  • 所以二维数组用的不是特别多

05_0800 C程序内存布局

环境变量PATH举例
windows下修改环境变量: 高级系统设计->高级->环境变量
linux下修改环境变量: export PATH=/home/uxingzaicpp/tmp4del/123PATH

  • 在环境变量里面的程序,可以直接打开;
    • 无论在哪个路径下都可以直接打开程序;
  • C语言可以获取环境变量的值:getenv
  • 命令行参数:echo $PATH;第0个命令行参数,就是程序名称;

 argv[0]是命令行第一个参数,就是程序自己的名字;

参数arg


C程序内存布局

 

 

  •  可能难的不是指针,而是搞不懂内存;
  • 从宏观的方向去理解指针;

 代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=46

回到目录

05_0801 5.8 指针数组的初始化 5.9 指针与多维数组

5.8 指针数组的初始化

  • int a[12]
  • int* a[12] 就是一个指针数组
  • 指针指向哪里都可以;*p读地址的内容,就要考虑了。

5.9 指针与多维数组
矩阵下标的计算公式: 列宽*row + col

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=47

回到目录

05_0900 5.10 命令行参数


char **argv 与 char* argv[] 作为函数参数等价,正如char* s与char s[]一样

char* argv[]     char* s

char* *argv      char s[]
         

int main(int argc, char* argv[])
{
        char* v[] =
        {
                "myecho.exe",
                "hello,",
                "world",
                NULL,
        };// 这是一个指针数组,每个元素是一个指针,所以大小应该是3或者4;每个p+1,就指向了下一个内容

        char** p= v;
        char* a = *(++p);


        //对比q讲解argv
}    

         
   

  • 如何自己解析:argv[]的内容;获取输入的字符串?『可以自己写的』
    • 解析字符串——必须要掌握的技能;具有的起码的能力   
  • *++argv是啥意思?
    • *(++argv)指针先+1;
  • v是一个指针数组,数组中的每一个内容是一个指针;
  • p指向了v的第一个数组指针;
  • char* a = *(++argv);    argv本来是二级指针,*argv就变成了一级指针;
    • 所以a指向了每一个字符串;
    • a和p都是指向了hello,很奇怪;为什么*(++p)导致a和p都指向了下一个字符串呢?
  • 解释 *++argv (*++argv)[0] *++argv[0]
    • 不建议写这种复杂不容易读的东西
  • []与操作数结合的优先级比*和++高

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=48

回到目录

  • find同linux下的grep函数作用相当;
    • cat 123 | grep  |的意思是将cat的输出作为grep的输入;
    • grep -n ld 输出ld的行号;
    • grep -v ld 反向输出不含有Ld的行;
    • grep -vn ld
  • 书上的find函数
#include <stdio.h>
#include <string.h>

#define MAXLINE 1000

int getline(char *line, int max);

/*find函数:打印与第一个参数指定的模式匹配的行*/
main(int argc, char *argv[])
{
    char line[MAXLINE];
    long lineno = 0;
    int c, except = 0, number = 0, found = 0;

    while (--argc > 0 && (*++argv)[0] == '-')
    {
        while (c == *++argv[0]) // 这里两种写法不一样,这里是挨个访问字符
        {
            switch (c)
            {
                case 'x':
                    except = 1;
                    break;
                case 'n':
                    number = 1;
                    break;
                default:
                    printf("find: illegal option %c\n", c);
                    argc = 0;
                    found = -1;
                    break;
            }
        }
    }

    if (argc != 1)
    {
        printf("Usage: find -x -n pattern\n");
    }
    else
    {
        while (getline(line, MAXLINE) > 0)
        {
            lineno++;
            if ((strstr(line, *argv) != NULL) != except)
            {
                if (number)
                    printf("%ld: ", lineno);
                printf("%s", line);
                found++;
            }
        }
    }

    return found;
}

int getline(char *line, int max)
{
    int c, i;

    for (i = 0; i < max - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
    {
        line[i] = c;
    }

    if (c == '\n')
    {
        line[i] = c;
        ++i;
    }
    line[i] = '\0';

    return i;

}

05_0901 5.10 命令行参数 - 练习

编写程序expr

编写程序tail

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=49

回到目录

tail 输出文本的末尾多少行;适用于读取最新的log日志;

05_1000 5.11指向函数的指针

函数指针里存放的值不是函数地址,而一个跳转指令

  • 一个函数是有地址的;char* p = (char *)myAdd;  p的值和myAdd的值略微有点不同;前面是一个e9指令
  • 这个跳转指令,最终会跳到函数地址
  • E9就是jmp:相对跳转指令
  • 计算公式:E9后面的地址 = 目标地址myAdd - 当前地址p - 5

 从指针说起


1.定义一个指针变量,比如说int* a; 那么*a就可以改变指针a指向的地址的内容
2.定义一个函数变量,比如说func f; 那么(*f)就可以调用f指向的内容的函数, *可以省略
编写程序tail

  • 如何通过指针p去执行这个函数呢?

函数指针类型和int a没什么分别
普通函数秒变函数指针类型

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=50

 

回到目录

05_1001 5.11指向函数的指针 - 练习 5.12复杂声明的前5个例子

逆序排序


不区分大小写排序


指向二维数组的指针

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=51

 复杂声明

  •  int (*daytab)[13] 指向维维数组的一个指针;指向了datab[13]的指针;
  • 最后两个遇到的比较少;

第六章 结构

《跟着星仔学C语言》第六章 结构 - xingzaicpp - 博客园

06_0100 6.1 结构的基本知识 6.2 结构与函数

关键字struct后面的名字是可选的

结构体的sizeof与字节对齐

  • C++的类是从结构体扩展出去的;
  • 编译器喜欢对齐的内存;12个字节;
  • 多理解,多思考;少死记硬背;少当规则处理;多应用;
  • arm访问不是字节对齐的地址,就会崩溃;


结构体的初始化可以在定义的后面使用初值表进行
struct point maxpt = {320, 200};

结构体的合法操作
1.整体的复制和赋值
2.通过&运行取地址
3.访问其成员
4.结构体无法直接进行比较是否相等(但有其他方法)

memcmp可以比较:比较内存;比较两个内存是否相等;<string.h>

  • memcmp(&x, &t, sizeof(struct point));   比较两个结构体的内存是否相等;

p->结构成员快速访问结构成员

在函数参数中, 结构体参数和普通参数一样,都是传值调用

  • 拷贝了一份到堆栈里面;

如果返回值是结构体,那也是对结构体进行了拷贝

  • 之前返回Int是在一个寄存器里面放堆栈的返回值;
  • 但是结构体的话,寄存器不够用,寄存器只有4个字节;
  • 效率就是比较低的;
  • 现在的计算机比较高;有些不是极致优化的代码也不是大问题;
  • 传指针;

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=52

回到目录

06_0200 6.3 结构数组

exe的生成过程
参考:05_0300 5.3 指针与数组

  • sizeof 计算数组长度;动态计算;不需要因为类型而改变;微软有 ARAYSIZE宏

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=53

回到目录

06_0300 6.4 指向结构的指针

1字节对齐
参考:05_0400 5.4 地址算术运算

地址随便算,只要你知道你在干什么就行
个人觉得, &tab[-1]和&tab[n] 都是有效的,但你要知道指针指到哪里去了

  • java没有指针;有时候需要将指针改为java;
  • 结构体中还有字节对齐;注意其地址计算的问题;

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=54

回到目录

06_0400 6.5 自引用结构

二叉树
1.任意结点的左子树比该结点小,右子树比该接点大
2.二叉树的特殊形式就是链表

  • 统计单词次数
  • 二叉树
  • 结构体包含自身的实例是非法的;
    • 定义一个结构体必须要知道自己的大小是多大?但是left和right就不知道了
    • 所以只能搞一个指针;指针的大小总是固定的;所以叫做自引用结构。

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=55

回到目录

06_0500 6.6.0 单链表

单链表的构造
单链表的查找

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=56

回到目录

06_0510 6.6.1 删除节点与哈希

1.从单链表中删除一个结点
2.讲解计算机领域内的hash是什么意思

  • 如果数据规模太大的怎么办?
    • 降低数据规模;通过长度来降低数据规模
  • 哈希,主要是为了让查找变得更加高效;
  • 比较好的哈希算法是:散列列表比较均匀;这样啊每次的查找时间都差不多;就最好;

 

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=57

回到目录

06_0520 6.6.2 插入节点与表查找

1.在单链表中插入结点
2.讲解书中6.6节的内容

代码:http://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=58

回到目录

06_0600 6.7 类型定义(typedef)

1.typedef 重定义基本数据类型
2.typedef 重定义字符串
3.typedef 重定义结构体
4.typedef 重定义函数指针

  • 把现有类型用一个新的类型来表示

 

  •  typedef 重定义函数指针
    •  

 

代码:https://q1024.com/p/item.php?u=krc
视频:https://www.bilibili.com/video/BV12L4y1Y76R?p=59

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值