学习过程中部分c语言疑惑问题的代码验证

本文深入探讨了C语言中的高级主题,包括指针操作、内存管理、数据类型大小、链表操作、字符串处理等,提供了丰富的示例代码,帮助读者理解和掌握C语言的复杂特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所有代码均为曾经学习过程中不清楚不或明白的问题,做的验证,拿出来分享,文章有点长哦
内容涉及(按文章中的先后顺序):argc和argv;const;getmemory;各种数据类型所占用内存大小;pragma和error;sizeof(*p)和sizeof§;sizeof和strlen;void指针;volatile;while(–a)和while(a–);单链表;双链表;猴子吃桃;阶乘;矩阵乘法;类型转换;冒泡选择排序;大小端;数组首地址表示方法;数组作为结构体变量;数组作为结构体成员;数组作为形参;野指针;指针访问二维数组;指针分配动态内存;指针函数与函数指针;指针数组;质数、最小公倍数、最大公约数、闰年;字符串逆序输出;字符串常量与字符数组;

1.argc和argv

//#include "stdafx.h"
//#include "iostream"

#include<stdio.h>
//using namespace std;

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

	for (i = 0; i < argc; i++)
	{
		//cout << "argument" << i << ": " << argv[i] << endl;
		printf("argv[%d] = %s\n",i,argv[i]);
	}
	//cout << "total argument:" << argc;
	printf("argc = %d\n", argc);

		//return EXIT_SUCCESS;
		return 0;
}
*/

/*
两个参数中第一个参数argc表示在Dos命令行中的输入的程序名和参数个数之和,第二个参数中argv[0]记录的程序名,后面的argv[i]记录的输入参数。
另外argc argv是标识符,可以修改名称。
*/

2.const

/*
#include<stdio.h>
int main(void)
{
	const int a = 12;
	const int* p = &a;
	p++;
 &b;
	t++;
	p = &b;
	q = &b;	p--;
	int const* q = &a;
	int b = 12;
	int* const r = &b;
	r++;
	const int* const t =
	return 0;
}
*/
/*

#include<stdio.h>
int main()
{ const int a = 12;
const int* p = &a; //这个是指向常量的指针, 指针指向
一个常量
p++; //指针可以自加、 自减
p--; //合法
int const* q = &a; //这个和上面的“const int*p=&
a; ”是一个意思
int b = 12;
int* const r = &b; //这个就是常量指针(常指针) , 不
能自加、 自减, 并且要初始化
//r++; //编译出错
const int* const t = &b; //这个就是指向常量的常指
针, 并且要初始化, 用变量初始化
//t++; //编译出错
p = &b; //const指针可以指向const和非const对象
q = &b; //合法
return 0;
}
*/
/*
#include<stdio.h>
int main(void)
{
	int a = 12;
	int b = 13;
//第一种
	//const int* p = &a;	//或者int const*p=&a;		指向常量的指针,指针的地址可以改,但不能对指针内的数据进行改动,因为这时* p  是一个常量,但	指针p	可以改动
	//可以在后面这么写,(1)	p = &b;	(2)	a = 13;	(3)p++;这三种是合法的	不合法的一种为(1)*p=13;
//第二种
	int* const p = &a;		//常量指针,指针p为常量,此时指针p为常量,所指向的地址不能改动,但指针所指地址数据可以改动
	//可以在后面这样写,(1)a=13;	(2)*p=13;这两种都是合法的   不合法的两种为(1)p = &b;	(2)p++;
//第三种
	//const int* const p= &a;		//指向常量的常指针,并且要初始化,用变量初始化,*p   指针p都是只读的,不可改动
	//可以在后面这样写,(1)a=13;这种是合法的,不合法的三种为(1)p = &b;	(2)*p = 13;	(3)p++;
	//a = 13;
	//p = &b;
	//*p = 13;
	//p++;
	printf("%d\n", *p);
}
*/

3.getmemory

//#pragma warning( disable : 4996)

/*
void getmemory(char *p)
   {
  p=(char *) malloc(100);
  strcpy(p,"hello world");
   }
   int main( )
   {
  char *str=NULL;
  getmemory(str);
  printf("%s/n",str);
  free(str);
  return 0;
	}
 程序崩溃,getmemory中的malloc 不能返回动态内存, free()对str操作很危险
77、void GetMemory(char *p)
 {
  p = (char *)malloc(100);
 }
 void Test(void)
 {
  char *str = NULL;
  GetMemory(str);
  strcpy(str, "hello world");
  printf(str);
 }

 请问运行Test函数会有什么样的结果?
 答:程序崩溃。因为GetMemory并不能传递动态内存,Test函数中的 str一直都是 NULL。strcpy(str, "hello world");将使程序崩溃。

 void GetMemory2(char **p, int num)
 {
  *p = (char *)malloc(num);
 }
 void Test(void)
 {
 char *str = NULL;
 GetMemory(&str, 100);
 strcpy(str, "hello");
 printf(str);
 }
 请问运行Test函数会有什么样的结果?
 答:(1)能够输出hello
	 (2)内存泄漏,因为忘记free
78、char *GetMemory(void)
 {
  char p[] = "hello world";
  return p;
 }
 void Test(void)
 {
  char *str = NULL;
  str = GetMemory();
  printf(str);
 }
 请问运行Test函数会有什么样的结果?
 答:可能是乱码。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,
 但其原先的内容已经被清除,新内容不可知。
 ---------------------------------------
  下面程序运行有什么样的结果?

	char *GetString(void)

	{

	  char array[6];

	  strcpy(array, “hello”);

	  return array;

	}

	void main()

	{

	  char *pstr = NULL;

	  pstr = GetString();

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

	}

	答对这个问题,脑子里必须有一根弦,那就是栈内存里的内容如“昙花一现”,其有效的生命周期只等于函数周期,访问栈内存的前提条件是在函数运行范围内,函数一旦退出,相应的栈内存立马消散,如同镜花水月,再访问就是野指针的“非法”操作。

	上例中,函数char *GetString返回了指向局部数组array[6]的指针,但array[6]位于栈中,函数的栈内存在其结束后立刻失效,函数退出后再试图通过pstr访问栈内存就成了非法操作。因此,返回指向栈内存的指针这种机制,看似在函数退出后继续访问栈内存留了一个“后门”,实际上是一个隐晦的陷阱。再比较下面例子:
	*/
/*
#define _CRT_SECURE_NO_WARNINGS
#pragma warning( disable : 4996)
#include<stdio.h>
#include<string.h>
#include<malloc.h>


	char *GetString(void)

   {

	  char array[6]="hello";

	  char *p = (char *)malloc(6);

	  strcpy(p, array);

	  return p;

	}

	int main(void)

	{

	  char *str = NULL;

	  str = GetString();

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

	  free(str);

	  return 0;
	  

	}
	
	
	这里先把hello字符串从array[]所在的栈内存拷贝到指针p指向的堆内存中,当GetString 函数结束后,array[]栈内存失效了,但指针p所指的堆内存仍有效并保存着”hello”串,函数返回指向堆的指针就没有问题了。如果把直接用指针指向字符串呢:

	char *GetString(void)

	{

	  char *p = "hello";

	  return p;

	}

	void main()

	{

	  char *str = NULL;

	  str = GetString();

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

	}

	把原先的数组修改成指针,即 char *str="hello",这样也可行,因为"hello"串位于常量数据区,在子函数结束后依然有效,通过返回的p指针仍然可以读取这块内存。
	 栈本身主要是由编译器静态维护和管理,所以程序中一般情况下应该避免使用指向栈的指针
---------------------------------------------
79、void Test(void)
 {
   char *str = (char *) malloc(100);
  strcpy(str, “hello”);
  free(str);
  if(str != NULL)
  {
	strcpy(str, “world”);
    printf(str);
   }
 }
 请问运行Test函数会有什么样的结果?
 答:篡改动态内存区的内容,后果难以预料,非常危险。因为free(str);之后,str成为野指针,
 if(str != NULL)语句不起作用。
 野指针不是NULL指针,是指向被释放的或者访问受限内存指针。
 造成原因:指针变量没有被初始化任何刚创建的指针不会自动成为NULL;
	 指针被free或delete之后,没有置NULL;
	 指针操作超越了变量的作用范围,比如要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
*/

4各种数据类型所占用内存大小

/*
#include<stdio.h>
#include<stdlib.h>

int main()
{
	int n = 0;														//整型
	char ch = 0;													//字符
	int arr1[5] = { 0 };											//整型数组
	char arr2[5] = { 0 };											//字符数组
	int* p1 = &n;													//整形指针
	char* p2 = &ch;													//字符指针
	int* p3 = arr1;													//整形指针
	char* p4 = arr2;												//字符指针
	int(*p5)[5] = &arr1;											//整形数组指针
	char(*p6)[5] = &arr2;											//字符数组指针
	int* arr3[5] = { p1 };											//整形指针数组
	char* arr4[5] = { p2 };											//字符型指针数组
	printf("int占%d个字节\n", sizeof(int));
	printf("short占%d个字节\n", sizeof(short));
	printf("char占%d个字节\n", sizeof(char));
	printf("long占%d个字节\n", sizeof(long));
	printf("float占%d个字节\n", sizeof(float));
	printf("long long占%d个字节\n", sizeof(long long));
	printf("double占%d个字节\n", sizeof(double));
	printf("整形变量占%d个字节\n", sizeof(n));
	printf("字符变量占%d个字节\n", sizeof(ch));
	printf("整形数组 int [5] 占%d个字节\n", sizeof(arr1));
	printf("字符数组 char [5] 占%d个字节\n", sizeof(arr2));
	printf("整形指针 int* 占%d个字节\n", sizeof(p1));
	printf("字符指针 char* 占%d个字节\n", sizeof(p2));
	printf("整形数组指针 占%d个字节\n", sizeof(p5));
	printf("字符数组指针 占%d个字节\n", sizeof(p6));
	printf("整形指针数组 占%d个字节\n", sizeof(arr3));				//指针4个字节,数组5个字节4*5=20
	printf("字符指针数组 占%d个字节\n", sizeof(arr4));				//指针4个字节,数组5个字节4*5=20
	printf("指向整形数组的指针 占%d个字节\n", sizeof(p3));
	printf("指向字符数组的指针 占%d个字节\n", sizeof(p4));



	//system("pause");
	return 0;
}
*/
/*
#include<stdio.h>
#include<stdlib.h>

int main()
{
	printf("int占%d个字节\n", sizeof(int));
	printf("short占%d个字节\n", sizeof(short));
	printf("char占%d个字节\n", sizeof(char));
	printf("long占%d个字节\n", sizeof(long));
	printf("float占%d个字节\n", sizeof(float));
	printf("double占%d个字节\n", sizeof(double));
	printf("long long占%d个字节\n", sizeof(long long));

	printf("\n");

	printf("unsigned char占%d个字节\n", sizeof(unsigned char));
	printf("unsigned int占%d个字节\n", sizeof(unsigned int));
	printf("unsigned long占%d个字节\n", sizeof(unsigned long));
	printf("unsigned short占%d个字节\n", sizeof(unsigned short));
	printf("unsigned float占%d个字节\n", sizeof(unsigned float));//"unsigned": 不能与类型 "float" 一起使用
	printf("unsigned double占%d个字节\n", sizeof(unsigned double));//	"unsigned": 不能与类型 "double" 一起使用	




	//system("pause");
	return 0;
}
*/
/*
1、unsigned的作用就是将数字类型无符号化, 例如 int 型的范围:-2^31 ~ 2^31 - 1,而unsigned int的范围:0 ~ 2^32。
   看起来unsigned 是个不错的类型,尤其是用在自增或者没有负数的情况。但是在实际使用中会出现一些意外的情况。

2、signed在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类型的话就需要在类型前加上unsigned。
   无符号版本和有符号版本的区别就是无符号类型能保存2倍于有符号类型的正整数数据。


unsigned和signed的区别

1、所有比int型小的数据类型(包括char,signed char,unsigned char,short,signed short,unsigned short)转换为int型。
如果转换后的数据会超出int型所能表示的范围的话,则转换为unsigned int型

2、bool型转化为int型时,false转化为0,true转换为1;反过来所有的整数类型转化为bool时,0转化为false,其它非零值都转为true

3、如果表达式中混有unsigned short和int型时,如果int型数据可以表示所有的unsigned short型的话,则将unsigned short类型的数据转换为int型,
否则,unsigned short类型及int型都转换为unsigned int类型

举个例子,在32位机上int是32位,范围–2,147,483,648 to 2,147,483,647,unsigned short是16位,范围0 to 65,535,
这样int型的足够表示unsigned short类型的数据,因此在混有这两者的运算中,unsigned short类型数据被转换为int型

4、unsigned int 与long类型的转换规律同3,在32位机上,unsigned int是32位,范围0 to 4,294,967,295,long是32位,范围–2,147,483,648 to 2,147,483,647,
可见long类型不够表示所有的unsigned int型,因此在混有unsigned int及long的表达式中,两者都被转换为unsigned long

5、如果表达式中既有int 又有unsigned int,则所有的int数据都被转化为unsigned int类型
*/

5.pragma和error

/*
#include<stdio.h>
#define STR
void main()
{
printf("学习#pragma命令中message参数的使用!\n");
#ifdef STR
#pragma message("STR已经定义过了")	//该指令用于在预处理过程中输出一些有用的提示信息
#endif
return;
}

可以利用#pragma pack来改变编译器的对齐方式:
#pragma pack(n)  //指定n字节对齐
#pragma pack()	 //取消对齐方式

#pragma warning//该指令允许选择性地修改编译器警告信息。
#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385)       // 4385号警告信息仅报告一次
#pragma warning(error:164)       // 把164号警告信息作为一个错

============================================================

#if(...)
	.....;
#elif
	.....;
#else
	#error(....) ;//指令让预处理器发出一条错误信息,并且会中断编译过程。

*/

6.return


/*
#include<stdio.h>
int test(int n);
int main()
{

	printf("%d\n", test(5));
	return 0;
}
int test(int n)
{

	if (3 == n)
		return 2;
	return 3;
}

*/

7.return和printf


//以求最小公倍和最大公约数的程序为例

//printf
/*
#include<stdio.h>
int max_gongbei(int, int);
int main(void)
{
	int a, b;
	printf("请输入两个数:");
	scanf_s("%d%d", &a, &b);
	max_gongbei(a, b);
}
int max_gongbei(int a, int b)
{
	int c = a % b, m, n;
	m = a;
	n = b;
	while (0 != c)
	{
		a = b;
		b = c;
		c = a % b;
	}
	printf("%d和%d的最大公约数为%d\n", m, n, b);
	printf("%d和%d的最小公倍数为%d\n", m, n, (m * n / b));
	return 0;
}
*/


//return
/*
#include<stdio.h>
int max_gongbei(int, int);
int main(void)
{
	int a, b;
	printf("请输入两个数:");
	scanf_s("%d%d", &a, &b);
	//max_gongbei(a, b);
	printf("%d和%d的最大公约数为%d\n", a, b, max_gongbei(a, b));
	printf("%d和%d的最大公倍数为%d\n", a, b, a*b/max_gongbei(a, b));
}
int max_gongbei(int a, int b)
{
	int c = a % b, m, n;
	m = a;
	n = b;
	while (0 != c)
	{
		a = b;
		b = c;
		c = a % b;
	}
	return b;
}
*/

8.sizeof(*p)和sizeof§

/*
#include <stdio.h>

 int main(void)
 {
	//一维数组
	char ca[] = "123456";
	char *pca = "123456";
	printf("sizeof(ca)=%d,sizeof(pca)=%d,sizeof(*pca)=%d\n",sizeof(ca),sizeof(pca),sizeof(*pca));
	//二维数组
	char cb[3][7] = {"123456","123456","123456"};
	char *pcb[] = {"123456","123456","123456"};
	printf("sizeof(cb)=%d,sizeof(pcb)=%d,sizeof(*pcb)=%d,sizeof(**pcb)=%d\n",sizeof(cb),sizeof(pcb),sizeof(*pcb),sizeof(**pcb));
	return 0;
}
*/



 /*
 sizeof(数组名)返回的是数组的大小

sizeof(ca)=7,注意此处包含一个结束符
sizeof(cb)=18,

sizeof(一维数组指针)返回的是数组单个元素指针的大小,即系统指针的长度,32位系统为4,64位系统位8

64位:sizeof(pca)=8,32位:sizeof(pca)=4

sizeof(*一维数组指针)返回的是数组单个元素对应类型的大小

sizeof(*pca)=1,char的大小

sizeof(二维数组指针)返回的是二维数组行指针的大小,32位系统为行数×4,64位系统位行数×8

sizeof(pcb)等同于sizeof(pcb[0])、sizeof(pcb[1])、sizeof(pcb[2])

64位:sizeof(pcb)=24,3行×8,注意二维数组每一行的结尾没有结束符'\0',所以是行长度为8

32位:sizeof(pcb)=12,3行×4,

sizeof(*二维数组指针)返回的是系统指针的长度,32位系统为4,64位系统位8

64位:sizeof(pcb)=8,32位:sizeof(pcb)=4

sizeof(**二维数组指针)返回的是数组单个元素指针的大小,即char的大小

sizeof(**pcb)=1,

 */

9.sizeof和strlen

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

int main(void)
{
	char a[7] = "ILOVEC";
	//int a[7] = {1,2,3,4,5,6,7};
	printf("str[%d]\n", strlen(a));
	printf("siz[%d]\n", sizeof(a));
	printf("siz2[%d]\n", sizeof(a)/sizeof(a[0]));
	return 0;

}
*/

10.void指针

/*
#include<stdio.h>
int main(void)
{
	void* n;
	int a = 9;
	int* pa;
	pa = &a;
	n = &a;
	printf("pa=%d\t*pa=%d\t", pa, *pa);
	pa++;//只能做测试用,因为这里pa++后pa所指向的地址不知道是否可用,无意义
	//printf("%d", n++);		//不能n++;也不能int*n++;即void*类型的指针不能++
	printf("pa++=%d\t", pa);
	printf("*(int*)n=%d\t", *(int*)n);
}

*/

11.volatile

/* 
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如

操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行

优化,从而可以提供对特殊地址的稳定访问。
*/



/*
int gettimeofday(struct timeval* tv, struct timezone* tz);//原型
#include<sys/time.h>//头文件,如果成功获取当前时间, 那么返回0, 否则返回 - 1, 错误代码存于errno中
//gettimeofday() 会把目前的时间通过tv所指的结构返回, 并将当地时区的信息放到tz所指的结构中。
struct timeval{
	long tv_sec;//存放的是秒
	long tv_usec;//存放的是微秒
};
struct timezone {
	int tz_minuteswest;//存放的是与格林威治时间的时差
	int tz_destime;//存放的是时间的修正方式
};
*/
/*
#include<stdio.h>
#include<sys/time.h>
int main(int argc, char* argv[])
{
	struct timeval start, end;
	gettimeofday(&start, NULL);
	double timeuse;
	int j;
	for (j = 0; j < 1000000; j++);
	gettimeofday(&end, NULL);
	timeuse + 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec-start.tv_usec;
	timeuse /= 1000000;
	printf("运行时间为:%f\n", timeuse);
	return 0;
}
*/
/*
#include<stdio.h> 
void main()
{
	volatile int i = 10;
	int a = i;
	printf("i= %d\n", a);
	__asm {
		mov dword ptr[ebp - 4], 20h
	}
	int b = i;
	printf("i= %d\n", b);
}
*/
/*
volatile int i = 10;
int a = i;
。。。//其他代码,并未明确告诉编译器,对i进行过操作
int b = i;

volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的

汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间

的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果

i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
*/


/*
__asm { mov dword ptr [ebp-4], 50h}
ebp-4是个地址,这个地址指向的内存是一个dword型变量,将0x50这个数值放到这个变量中。
*/


/*
关键字volatile有什么含意 ? 并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
•; 并行设备的硬件寄存器(如:状态寄存器)
•; 一个中断服务子程序中会访问到的非自动变量(Non - automatic variables)
•; 多线程应用中被几个任务共享的变量



回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
•; 一个参数既可以是const还可以是volatile吗?解释为什么。
•; 一个指针可以是volatile 吗?解释为什么。
•; 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}

下面是答案:
•; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
•; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
•; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:


int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}


由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
*/

12.while(–a)和while(a–)

/*
#include<stdio.h>
int main(void)
{
	int a = 3, sum = 0;
	do
	{
		sum += a * a;

	} while (--a);			//与whil(a--)效果相同
	printf("%d\n", sum);	//sum=14
}
*/

/*
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a = 3, sum = 0;
	while (a--)				//与while(--a)效果相同
	{
		sum += a * a;

	};
	printf("%d\n", sum);	//sum=5
	exit(0);				//执行exit( ) 函数只能够返回到main( ) 函数的调用处,执行exit() 函数之后将直接结束整个程序的运行
							//当exit() 中的参数为0时, 表示正常退出,其他返回值表示非正常退出
							//执行exit() 函数意味着进程结束; 而return仅表示调用堆栈的返回, 其作用是返回函数值, 并且退出当前执行的函数体, 返回到函数的调用处
							//在main() 函数中, return n和exit(n) 是等价的
}
*/

/*
#include <stdio.h>
int main(void)
{
	char ch;
	int i;
	float fl;
	fl = i = ch = 'C'; 
	printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); 
	ch = ch + 1; 
	i = fl + 2 * ch; 
	fl = 2.0 * ch + i; 
	printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); 
	ch = 1107; 
	printf("Now ch = %c\n", ch); 
	ch = 80.89;
	printf("Now ch = %c\n", ch);
	return 0;
}
*/

13.单链表


/*
#include<stdio.h>
#include <stdlib.h>
#include <memory.h>

struct list
{
	int data;
	struct list *pNext;
};

struct list * create_list(int);
void new_node(struct list *, struct list *);
void insert_head(struct list *, struct list *);
void traverse_list(struct list *);
void traverse_list2(struct list *);
int delete_node(struct list *, int);
void reverse_linkedlist(struct list *);

int main(void)
{
	struct list *pHead = create_list(0);

	//new_node(pHead, create_list(1));
	//new_node(pHead, create_list(2));
	//new_node(pHead, create_list(3));

	insert_head(pHead, create_list(11));
	insert_head(pHead, create_list(12));
	insert_head(pHead, create_list(13));
	



/
	//创建第一个节点
	struct list *p = (struct list *)malloc(sizeof(struct list));
	if (NULL == p)
	{
		printf("malloc error\n");
		return -1;
	}

	memset(p, 0, sizeof(struct list));
	p->data = 1;
	p->pNext = NULL;

	pHead = p;

	//创建第二个节点
	struct list *p1 = (struct list *)malloc(sizeof(struct list));
	if (NULL == p1)
	{
		printf("malloc error\n");
		return -1;
	}

	memset(p1, 0, sizeof(struct list));
	p1->data = 2;
	p1->pNext = NULL;

	p->pNext = p1;

	//创建第三个节点
	struct list *p2 = (struct list *)malloc(sizeof(struct list));
	if (NULL == p2)
	{
		printf("malloc error\n");
		return -1;
	}

	memset(p2, 0, sizeof(struct list));
	p2->data = 3;
	p2->pNext = NULL;

	p1->pNext = p2;
/

printf("第一个节点的数据:%d\n", pHead->data);
printf("第二个节点的数据:%d\n", pHead->pNext->data);
printf("第三个节点的数据:%d\n", pHead->pNext->pNext->data);
printf("第四个节点的数据:%d\n", pHead->pNext->pNext->pNext->data);

traverse_list2(pHead);
//delete_node(pHead, 12);



reverse_linkedlist(pHead);
traverse_list2(pHead);

return 0;
}

struct list * create_list(int data)
{
	struct list *p = (struct list *)malloc(sizeof(struct list));
	if (NULL == p)
	{
		printf("malloc error.\n");
		return NULL;
	}
	// 清理申请到的堆内存
	memset(p, 0, sizeof(struct list));
	// 填充节点
	p->data = data;
	p->pNext = NULL;

	return p;
}

void new_node(struct list *pH, struct list *pnew)
{
	int count = 0;
	//尾部加节点
	struct list *p = pH;
	while (NULL != p->pNext)
	{
		p = p->pNext;
		count++;		
		//第一次pH->pNext=NULL所以第一次不执行while循环,也就保证了第一个节点的数据初始时为0,往后再添加节点时才开始计数
	}
	p->pNext = pnew;
	pH->data = count + 1;
}

void insert_head(struct list *pH, struct list *pnew)
{
	//插入头节点
	struct list *p = pH;
	pnew->pNext = p->pNext;
	p->pNext = pnew;
	pH->data += 1;
}

void traverse_list(struct list *pH)
{
	//遍历节点
	int count = 1;
	struct list *p = pH->pNext;
	//printf("shkdjf %d\n", p->data);
	while (NULL != p->pNext)
	{
		//相当于除了头指针和头节点之外默认至少有一个有效节点,因为头指针为pH,头节点为pH->pNext,第一个有效节点为pH->pNext->pNext
		//printf("shkdjf %d\n", p->data);
		//第一次判断pH->pNext->pNext(第三个节点)
		//第二次判断pH->pNext->pNext->pNext(第四个节点)
		//第三次判断pH->pNext->pNext->pNext->pNext(为NULL,不执行循环)
		count += 1;
		printf("第【%d】个节点数据为【%d】\n", count, p->data);
		//第一次是pH->pNext->data
		//第二次是pH->pNext->pNext->data
		//之所以没有打印出最后一个是因为,下边的p = p->pNext;才把p指针移动到下一位
		p = p->pNext;
	}
	//printf("第【%d】个节点数据为【%d】\n", count+1, p->data);
}

void traverse_list2(struct list *pH)
{
	struct list *p = pH;
	while (NULL != p->pNext)
	{
		p = p->pNext;
		printf("节点为%d\n", p->data);
	}
}

int delete_node(struct list *pH, int data)
{
	//删除节点
	struct list *p = pH;
	struct list *pPreview = NULL;
	while (NULL != p->pNext)
	{
		pPreview = p;
		p = p->pNext;
		if (p->data == data)
		{
			if (NULL == p->pNext)
			{
				pPreview->pNext = NULL;
			}
			else
			{
				pPreview->pNext = p->pNext;
			}
			return 0;
		}
	}
		printf("not found the node");
		return -1;
}

void reverse_linkedlist(struct list *pH)
{
	//列表逆序
	struct list *p = pH->pNext;
	struct list *pBack;
	if (NULL == p->pNext || NULL == p)
	{
		return ;
	}
	else
	{
		while (NULL != p->pNext)
		{
			pBack = p->pNext;
			if (p == pH->pNext)
			{
				p->pNext = NULL;
			}
			else
			{
				p->pNext = pH->pNext;
			}
			pH->pNext = p;
			p = pBack;
		}
		insert_head(pH, p);
	}
	
}
*/

14.双链表

/*

#include<stdio.h>
#include <stdlib.h>

struct node
{
	int data;
	struct node *pPrev;
	struct node *pNext;
};

struct node *create_list(int);
void insert_tail(struct node *, struct node *);
void insert_head(struct node *, struct node *);
void head_traverse(struct node *);
void tail_traverse(struct node *);
int delete_node(struct node *, int );

int main(void)
{
	struct node *pHeader = create_list(0);

	//insert_tail(pHeader, create_list(1));
	//insert_tail(pHeader, create_list(2));
	//insert_tail(pHeader, create_list(3));

	insert_head(pHeader, create_list(11));
	insert_head(pHeader, create_list(12));
	insert_head(pHeader, create_list(13));

	printf("node is【%d】\n", pHeader->data);
	printf("node 1 is【%d】\n", pHeader->pNext->data);
	printf("node 2 is【%d】\n", pHeader->pNext->pNext->data);
	printf("node 3 is【%d】\n", pHeader->pNext->pNext->pNext->data);
	tail_traverse(pHeader);
	//struct node *p = pHeader->pNext->pNext->pNext;
	//head_traverse(p);
	printf("1*********1\n");
	delete_node(pHeader, 13);
	tail_traverse(pHeader);

	return 0;
}

struct node *create_list(int data)
{
	//创建节点
	struct node *p = (struct node *)malloc(sizeof(struct node));
	if (NULL == p)
	{
		printf("malloc error\n");
		return NULL;
	}
	else
	{
		p->data = data;
		p->pPrev = NULL;
		p->pNext = NULL;
	}
	return p;
}
void insert_tail(struct node *pH, struct node *pNew)
{
	//尾插入节点
	struct node *p = pH;
	while (NULL != p->pNext)
	{
		p = p->pNext;
	}
	p->pNext = pNew;
	pNew->pPrev = p;
}
void insert_head(struct node *pH, struct node *pNew)
{
	//头节点插入
	int count = 0;
	pNew->pNext = pH->pNext;
	if (NULL != pH->pNext)
	{
		count++;
		pH->pNext->pPrev = pNew;
	}
		pH->pNext = pNew;
		pNew->pPrev = pH;
		pH->data = count + 2;
}
void head_traverse(struct node *pTail)
{
	//头遍历节点(前向遍历)
	struct node *p = pTail;
	while (NULL != p->pPrev)
	{
		printf("node is【%d】\n", p->data);
		p = p->pPrev;

	}

}
void tail_traverse(struct node *pH)
{
	//尾遍历节点(后向遍历)
	struct node *p = pH;
	while (NULL != p->pNext)
	{
		p = p->pNext;
		printf("node is【%d】\n", p->data);

	}

}
int delete_node(struct node *pH, int data)
{
	//删除节点
	struct node *p = pH;

	while (NULL != p->pNext)
	{
		p = p->pNext;
		if (p->data == data)
		{
			if (NULL == p->pNext)
			{
				p->pPrev->pNext = NULL;
			}
			else
			{
				p->pPrev->pNext = p->pNext;
				p->pNext->pPrev = p->pPrev;
			}
			free(p);
			return 0;
		}
	}
	printf("not found node\n");
	return -1;
}
*/

15.猴子吃桃

//将一堆桃子平均分成5份正好多1个,第一次拿掉其中的一份后+1个后,再把剩下的平分5份正好多一个,一直这样,第五次,正好拿了n个,至少有多少个桃
/*
#include<stdio.h>
int taozi(int);

int main(void)
{
	int n;
	printf("请输入第5次拿的个数n:");
	scanf_s("%d", &n);
	taozi(n);
}
int taozi(int n)
{
	int sum = 4*n;
	//int sum = n;
		for (int i = 0; i < n; i++)
		{
			//sum = (sum) / (4 / 5) + 1;
			sum = (sum * 5) / 4 + 1;
			//sum = (sum - 1) * 5 + 1;
		}
		printf("桃子的总数为:%d\n", sum);
		return 0;
}
*/


/*
#include<stdio.h>
int main(void)
{
	int n;
	double sum ;
	printf("请输入第5次拿的个数n:");	//n=255
	scanf_s("%d", &n);
	sum = 4 * (double)n;
	//printf("桃子的总数为:%d\n", sum);
	for (int i = 0; i < 5; i++)
	{
		sum = sum*1.25 + 1.0;
	}
	printf("桃子的总数为:%f\n", sum);
}
*/
/*
#include<stdio.h>
#include<math.h>
int main(void)
{
	int i = 0, n = 1, t = n;//n是总的桃子数
	for (i = 0; i < 5; i++)
	{
		if (n % 5 == 1)
			n = n - (n / 5 + 1);
		else
			i =-1, n = ++t;//执行完这一句之后就要执行for循环中的i++;为了保证从i=0开始5次循环,所以先执行一次i=-1;这样i++后i=0
		//不管前面执行了几次循环,只要有一次不符合if就执行els这样i之前无论是几,执行else后就成为-1,再执行i++,i=0又重新循环判断

	}
	printf("桃子的总数为:%d\n", t);
}
*/

16.阶乘

//递归求阶乘
/*
#include<stdio.h>
int jiecheng(int);
int main(void)
{
	int a;
	printf("请输入一个数:");
	scanf_s("%d", &a);
	printf("%d的阶乘为%d", a, jiecheng(a));

}
int jiecheng(int n)
{
	if (0 == n || 1 == n)
		return 1;
	else
		return n * jiecheng(n - 1);
}
*/

//用for循环求
/*
#include<stdio.h>
void jiecheng(int);
int main(void)
{
	int a;
	printf("请输入一个数:");
	scanf_s("%d", &a);
	jiecheng(a);

}
void jiecheng(int n)
{
	int m = n;
	int c=1;
	if (0 == n || 1 == n)
	{
		c = 1;
		printf("%d的阶乘为%d", m, c);
	}
	else
	{
		for (int i = 2; i <= n; i++)
		{
			c = c * i;
		}
		printf("%d的阶乘为%d", m, c);
	}
}

*/

17.矩阵乘法

/*
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
	int** A;
	int** B;
	int r_A;
	int c_A;
	int r_B;
	int c_B;
	printf("The row of A:");
	scanf_s("%d", &r_A);
	printf("The col of A:");
	scanf_s("%d", &c_A);
	printf("The row of B:");
	scanf_s("%d", &r_B);
	printf("The col of B:");
	scanf_s("%d", &c_B);
	if (c_A != r_B) {
		printf("Wrong Matrix!\n");
		return 0;
	}
	A = (int**)malloc(r_A * sizeof(int*));
	for (int i = 0; i < r_A; i++) {
		A[i] = (int*)malloc(c_A * sizeof(int));
	}
	B = (int**)malloc(r_B * sizeof(int*));
	for (int i = 0; i < r_B; i++) {
		B[i] = (int*)malloc(c_B * sizeof(int));
	}
	printf("Now input the A.\n");
	for (int i = 0; i < r_A; i++) {
		for (int j = 0; j < c_A; j++) {
			scanf_s("%d", &(A[i][j]));
		}
	}
	printf("Now input the B.\n");
	for (int i = 0; i < r_B; i++) {
		for (int j = 0; j < c_B; j++) {
			scanf_s("%d", &(B[i][j]));
		}
	}
	int** ret;
	ret = (int**)malloc(r_A * sizeof(int*));
	for (int i = 0; i < r_A; i++) {
		ret[i] = (int*)malloc(c_B * sizeof(int));
	}
	for (int i = 0; i < r_A; i++) {
		for (int j = 0; j < c_B; j++) {
			ret[i][j] = 0;
			for (int k = 0; k < c_A; k++) {
				ret[i][j] += (A[i][k] * B[k][j]);
			}
		}
	}
	for (int i = 0; i < r_A; i++) {
		for (int j = 0; j < c_B; j++) {
			printf("%d\t", ret[i][j]);
		}
		printf("\n");
	}
	for (int i = 0; i < r_A; i++) {
		free(A[i]);
	}
	free(A);
	for (int i = 0; i < r_B; i++) {
		free(B[i]);
	}
	free(B);
	for (int i = 0; i < r_A; i++) {
		free(ret[i]);
	}
	free(ret);

	return 0;
}


*/

/*
#include <stdio.h>
int main()
{
	int a[3][2] = { 2,3,4,5,3,5 };
	int b[2][3] = { 1,2,3,4,5,6 };
	int c[3][3];
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			c[i][j] = 0;
			for (int k = 0; k < 2; k++)
				c[i][j] += a[i][k] * b[k][j];
		}
	}
	printf("矩阵1与矩阵2相乘的结果为:\n");
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", c[i][j]);
		}
		printf("\n");
	}
	return 0;
}
*/

18.类型转换



/*
#include<stdio.h>
int main(void)
{
	unsigned short i = 0;
	unsigned int a = 0;
	unsigned int b = 0;
	unsigned short j = 0;
	//unsigned long j = 0;
	while (i <unsigned short(j - 1))
		//j-1会隐式转换成int类型
	{
		printf("执行while succeed");
		break;
	}
}
*/

19.冒泡选择排序

/*
#include<stdio.h>

int main(void)
{

}


//冒泡排序
//比较相邻两个数
void bubble_sort(int arr[],int n)		
{
	int i, j, flag, temp;
	for (i = 0; i < n - 1; i++)
	{
		flag = 1;					//flag的作用见下面注释
		for (j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = 0;
			}
		}
		if (1 == flag)
			break;
	}
	printf(".......");
}
*/
/*
第三轮结束之后的比较结果就是最终的结果了, 没有必要再进行接下来的比较操作了, 但是计算机并不知道, 它还会进行接下来的比较操作。 为了改进冒泡法排序的这一缺点, 防止对已经完成
排序功能的数据进行没有任何意义的比较操作, 我们定义一个标识符flag, 在每轮比较操作之前将flag赋值为1, 如果在该轮比较操作中发现排序没有完成, 那么就在该轮结束时将其赋值为0, 
否则不改变其flag值, 在下一轮开始前对flag进行判断,如果其值为1, 表示在上一轮排序中发现已经完成了排序功能, 那么就结束冒泡法排序的比较操作。
*/

/*
//选择排序
//每次都比较第一个数和后面的数
void choice_sort(int a[], int n);
{
	int i, j, k, temp;
	for (i = 0; i < n - 1; i++)
	{
		k = 0;
		for (j = i + 1; j < n; j++)
		{
			if (a[j] < a[k])
			{
				k = j;
			}
			if (k != i)
			{
				temp = a[i];
				a[i] = a[k];
				a[k] = temp;
			}
		}
	}
}
*/

20判断大小端

/*
//第一种方法强制类型转换
#include<stdio.h>
int main(void)
{
	int a = 0x11223344;
	int* p;
	p = &a;
	if (0x44 == *(char*)p)	//必须将p转换成char型
	{
		printf("小端模式");
	}
	else
	{
		printf("大端模式");
	}
}
*/

/*
//第二种方法,共用体
	//共用体的特点:
	//1.union中可以定义多个成员,union的大小由最大的成员的大小决定;
	//2.union成员共享同一块大小的内存,一次只能使用其中的一个成员;
	//3.对某一个成员赋值,会覆盖其他成员的值;
	//4.联合体union的存放顺序是所有成员都从低地址开始存放。
#include <stdio.h>

union bit {		//对齐原则,char与int指向的均是低位地址 
	int a;
	char b;
};

int main() {
	bit test;
	test.a = 0x12345678;
	if (test.b == 0x78)		//本质还是强制类型转换
		printf("本机为小端模式\n");
	else
		printf("本机为大端模式\n");
	return 0;
}
*/

/*
//第三种 
void checkCpuMode(void)
 {
  int a = 0x12345678;
  if((char)a == 0x12)
   printf("big endian\n");
  else
   printf("little endian\n");
 }
*/

21.数组首地址表示方法

/*
也就是说, &c+1表示了该数组最后一个元素的下一个地址。,而&c 与 c 或者 &c[0] 一致,都表示该数组的首地址。
c和&c[0]都表示数组首元素首地址,c+1和&c[0]+1表示数组下一个元素的地址
&c表示整个数组的首地址,&c+1表示整个数组地址+1;
*/
/*
#include<stdio.h>
int main(void)
{
	int a[5];
	printf("a = %d\n", a);
	printf("&a[0] = %d\n", &a[0]);
	printf("&a = %d\n", &a);
	printf("===========================\n");
	printf("a+1 = %d\n", a+1);
	printf("&a[0]+1 = %d\n", &a[0]+1);
	printf("&a+1 = %d\n", &a+1);
	return 0;
}


输出结果
a = 16448460
& a[0] = 16448460
& a = 16448460
==========================
a + 1 = 16448464
& a[0] + 1 = 16448464
& a + 1 = 16448480
*/

22.数组作为结构体变量

/*
#include<stdio.h>
struct PP { float x, y, z; };
int main() {
	struct PP s[3] = { {1,4,5},{3,2,4},{5,3,7} };//数组作为结构体变量时有两种给数组赋值得得方式,一种是像这样定义时整体赋值,
			//第二种是定义完之后再在后面程序中赋值时,只能像第9行那样对每个元素赋值,而不能再整体赋值
	int i;
	printf("before:\n");
	//s[0].x = 1; s[0].y = 4; s[0].z = 5;
	for (i = 0; i < 3; i++) printf("%g %g %g\n", s[i].x, s[i].y, s[i].z);
	printf("after: \n");
	s[0].z=0; s[1].z=0; s[2].z=0; //变化
	for (i = 0; i < 3; i++) printf("%g %g %g\n", s[i].x, s[i].y, s[i].z);

	return 0;
}
*/

/*
struct rtc_time
{
	unsigned int year;
	unsigned int month;
	unsigned int date;			// 几号
	unsigned int hour;
	unsigned int minute;
	unsigned int second;
	unsigned int day;			// 星期几
};

struct rtc_time tWrite =
	{
		.year = 2015,
		.month = 8,
		.date = 9,
		.hour = 18,
		.minute = 11,
		.second = 3,
		.day = 0,
	};
*/

23.数组作为结构体成员

/*
#include<stdio.h>
#include<string.h>
int main(void)
{
typedef struct student {
		char name[20];
		char address[20];
		char school[30];
		int age;
		float score;
		char number[50];
	}stu;
stu a;
strcpy_s(a.name, "李明");
strcpy_s(a.address, "河北");
strcpy_s(a.school, "college");
a.age = 20;
a.score = 82.5;
strcpy_s(a.number, "1704102");
printf("name is %s\naddres is %s\nschool is %s\nage is %d\nscore is %f\nnumber is %s\n", a.name, a.address, a.school, a.age, a.score, a.number);
}
*/

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

void shuzu_jiegouti();

int main(void)
{
	shuzu_jiegouti();
}

void shuzu_jiegouti()
{
	typedef struct student {
		char name[20];
		char address[20];
		char school[30];
		int age;
		float score;
		char number[50];
	}stu;
	stu a;
	strcpy_s(a.name, "李明");
	strcpy_s(a.address, "河北");
	strcpy_s(a.school, "college");
	a.age = 20;
	a.score = 82.5;
	strcpy_s(a.number, "1704102");
	printf("name is %s\naddres is %s\nschool is %s\nage is %d\nscore is %f\nnumber is %s\n", a.name, a.address, a.school, a.age, a.score, a.number);
}
*/
/*
#include<stdio.h>
#include<string.h>

typedef struct student {
	char name[20];
	char address[20];
	char school[30];
	int age;
	float score;
	char number[50];
}stu;

stu a;
void shuzu_jiegouti(struct student);

int main(void)
{
	shuzu_jiegouti(a);
}

void shuzu_jiegouti(stu a)
{
	//stu a;
	strcpy_s(a.name, "李明");
	strcpy_s(a.address, "河北");
	strcpy_s(a.school, "college");
	a.age = 20;
	a.score = 82.5;
	strcpy_s(a.number, "1704102");
	printf("name is %s\naddres is %s\nschool is %s\nage is %d\nscore is %f\nnumber is %s\n", a.name, a.address, a.school, a.age, a.score, a.number);
}
*/

24.数组作为形参

/*
#include<stdio.h>
int fun(char* x)
{
	printf("%d\n", x);
	char* y = x;
	while (*y++);		//将指针y移置字符串最末端。即y指向'\0'
	printf("%d\n", y);
	return (y - x - 1);	//y - x为整个字符串 + '\0'的长度。y - x - 1即为字符串的长度。等效于strlen()函数。
}
void main()
{
	char a[] = "hello";
	printf("%c\n", a[0]);
	a[0]++;
	printf("%c\n", a[0]++);
	printf("%c\n", a[0]);
	printf("%d", fun(a));
}
*/

25.野指针

/*
#include<stdio.h>
int main(void)
{
	int* const p;//没有对p初始化,所以p为野指针
	*p = 4;
}
*/

26.指针访问二维数组

/*
#include<stdio.h>
int main(void)
{
	int a[3][3];
	int i, j;
	int* pa;
	printf("直接用二维数组a中的元素\n");       
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			a[i][j] = i * 3 + j + 1;
			printf("a[%d][%d]=%d\t", i, j, a[i][j]);
		}
		printf("\n");
	}
	pa = a[0];
	printf("%d\n", *pa);
	printf("%d\n", pa);
	printf("%d\n", pa+8);
	printf("%d\n", &a[2][2]);
	printf("通过指针引用二维数组a中的元素\n");
	for (i = 0, j = 0; pa <= &a[2][2]; pa++)
	{
		printf("a[%d][%d]=%d\t", i, j, a[i][j]);
		j++;
		if (3 == j)
		{
			j = 0;
			i++;
			printf("\n");
		}
	}
}
*/
//通过指针数组来引用数组时, 只要把数组中每行起始元素的首地址指给指针数组中的每个元素, 就可以实现对数组的引用了。
//在使用数组指针引用数组时, 只需要将数组元素的首地址赋值给定义的数组指针即可

27.指针分配动态内存

/*
#include<stdio.h>
#include<stdlib.h>
void main()
{
	int* pa = (int*)malloc(sizeof(int) * 5);		//int类型占4个字节 5*4=20字节
	int i;
	for (i = 0; i < 5; i++)							//指针类型占4个字节 4*5<=20
	{
		*(pa + i) = i + 1;
		printf("*(*pa+%d))=%d\n", i, *(pa + i));
	}
	free(pa);
}
*/
//切记不能对还没有指向内存的指针直接赋值(数值指针)
//编译器在编译的过程中会在内存中为字符串分配一个内存区域, 同时将分配的内存区域的首地址传递给指针变量, 因此在通过指针定义字符串时不必为其
//分配内存空间, 编译器在编译的过程中会自动完成对字符串内存的分配, 这与之前讲解的一般数值指针不同。
/*
#include<stdio.h>
int main(void)
{
	int a;
	int* pa = &a;
	*pa = 30;
	printf("*pa=%d\n", *pa);		//*pa所指向的内存空间是a所在的内存,所以不用释放内存,也不能释放,否则会报错
}
*/

28.指针函数与函数指针

//指针函数
/*
#include<stdio.h>
int* p1(int, int);
int main(void)
{
	int* p;
	p = p1(1, 2);
	printf("p1 result is %d\n", *p);
	return 0;
}
int* p1(int a, int b)
{
	int sum = a + b;
	return &sum;
}
*/

/*
//函数指针
#include<stdio.h>
int p1(int, int);
int main(void)
{
	int (*p2)(int a, int b);
	p2 = p1;		//也可以为p2=&p1;
	//p1是函数名,即是函数的首地址,所以可以p2=p1;
	//p2为int (*)(int a, int b)类型,p1为int (int a, int b)类型,所以可以p2=&p1;
	printf("add result is %d\n", p2(200, 300));
	return 0;
}
int p1(int a, int b)
{
	int sum = a + b;
	return sum;
}
*/

29.指针数组

/*
#include<stdio.h>
int main(void)
{
	char a[5] ;//等效于char* (&a) [5];
	char (*pa)[5],*pb;
	pa = &a;	//&a并不是一个char型指针,而是一个char*[5]类型的指针,所以定义*pa时不能定义成char *pa;,而应该定义成第6行那样
	pb = &a[0]; //&a[0]是一个char型的指针,就相当于char变量
	printf("char型数组指针pa做占用的内存大小为:% d\n",sizeof( * pa) );		//sizeof(*一维数组指针)返回的是数组单个元素对应类型的大小1*5=5
	printf("char型数组指针pb做占用的内存大小为:% d\n", sizeof(*pb));
	printf("字符数组指针 占%d个字节\n", sizeof(pa));					   //sizeof(一维数组指针)返回的是数组单个元素指针的大小,即系统指针的长度,32位系统为4,64位系统位8
	printf("字符数组指针 占%d个字节\n", sizeof(pb));
	printf("pa=%d\tpa+1=%d\n", pa, pa + 1);
	printf("pb=%d\tpb+1=%d\n", pb, pb + 1);;
	return 0;
}
*/

30.质数、最小公倍数、最大公约数、闰年

/*
判断x是否为质数
从2一直到x-1
x如果有质因数,肯定会小于x/2,所以从2到x/2
除了2以外的质因数都是奇数,所以三从3开始一直到x/2的所有奇数
其实只要从2一直尝试到根号x就可以了。因为x只要有因数必定有一个因数小于等于根号x
当x是质数的时候,x的所有的倍数必然是合数,如果x已经判断不是质数了,那么x的倍数也不是质数
*/

/*************************第一种*****************************/

/*
#include<stdio.h>
int main(void)
{
int i,j;
for(i=2;i<100;i++)
{
for(j=2;j<=(i/j);j++)   //其实只要从2一直尝试到根号x就可以了。因为x只要有因数必定有一个因数小于等于根号x
{
	if(!(i%j))          //i整除j,说明i不是质数,break
break;
}

if(j>(i/j))             //比如i=5时执行for(j=2;j<=(i/j);j++),第一次j=2,满足条件,不满足if,不break,j++,此时j=3,所以j>(i/j)即3>(5/3),与之前for循环中2<=(5/2)相对应
printf("%d",i);         //类似下面用k是否为0来判断
}
return 0;
}
*/

/**************************第二种****************************/

/*
#include<stdio.h>
int main(void)
{
int i,j,k=0;
for(i=2;i<100;i++)
{
	 k=0;
for(j=2;j<=(i/j);j++)   //其实只要从2一直尝试到根号x就可以了。因为x只要有因数必定有一个因数小于等于根号x
{
	if(i%j==0)          //i整除j,说明i不是质数,break
		{k=1;
	break;}
}
//if(j>(i/j))
if(k==0)
printf("%d\n",i);
}
return 0;
*/


/*
闰年
整百年时须是400的倍数,不是整百年时,须是4的倍数,闰年时2月份为29天,比平年多一天
思路;先判断是否为4的倍数,再判断是否为100的倍数,不是100的倍数则为闰年
若为100的倍数,须再次判断是否为400的倍数,是400的倍数则为闰年
*/
/*
#include<stdio.h>
int run_year(int);
int main(void)
{
	int n;
	printf("请输入年份:");
	scanf_s("%d", &n);
	run_year(n);
}
int run_year(int a)
{
	if ((0 == a % 100 && 0 == a % 400) || (0 != a % 100 && 0 == a % 4))
		printf("%d年是闰年", a);
	else
		printf("%d年不是闰年", a);
	return 0;
}
*/



/*
最小公倍数
除0以外,可以被这两个数同时整除的最小的数,如2和4的最小公倍数为4,6和9的最小公倍数为27
最小公倍数=两整数的乘积/两整数的最大公约数
思路:先求最大公约数,再求最小公倍数
1、a%b余数为c
2、c=0;b即为最大公约数,a即为最小公倍数
3、c!=0;a=b;b=c;重复回到步骤1
*/
/*
#include<stdio.h>
int max_gongbei(int,int);
int main(void)
{
	int a, b;
	printf("请输入两个数:");
	scanf_s("%d%d", &a, &b);
	max_gongbei(a, b);
}
int max_gongbei(int a, int b)
{
	int c = a % b,m,n;
	m = a;
	n = b;
	while (0 != c)
	{
		a = b;
		b = c;
		c = a % b;
	}
	printf("%d和%d的最大公约数为%d\n", m, n, b);
	printf("%d和%d的最小公倍数为%d\n",m,n,((m*n)/b));
	return 0;
}
*/
/*
最大公约数的第二种方法,但推荐上面用的那种方法
*/
/*
#include<stdio.h>
int main()
{
	int m, n, temp, i;
	printf("Input m & n:");
	scanf_s("%d%d", &m, &n);
	if (m < n)  //比较大小,使得m中存储大数,n中存储小数
	{ //交换m和n的值
		temp = m;
		m = n;
		n = temp;
	}
	for (i = n; i > 0; i--)  //按照从大到小的顺序寻找满足条件的自然数
		if (m % i == 0 && n % i == 0)
		{//输出满足条件的自然数并结束循环
			printf("The GCD of %d and %d is: %d\n", m, n, i);
			break;
		}

	return 0;
}
*/

31.字符串逆序输出

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

void Reverse(char *);
void Reverse1(char *, char *);
void Reverse2(char *);

int main(void)
{
	char str[100];
	char ptr[100];
	printf("Input a string:");
	gets_s(str);
	//Reverse(str);
	//Reverse1(str,ptr);
	Reverse2(str);
	//printf("Inversed results:%s\n", str);
	//printf("Inversed results:%s\n", ptr);
	printf("Inversed results:%s\n", str);

	return 0;
}


//第一种
void Reverse(char *str)
{
	char *s1, *s2,temp;
	int n = strlen(str);
	s1 = str;
	s2 = s1 + n - 1;
	while (s1<s2)
	{
		temp = *s1;
		*s1 = *s2;
		*s2 = temp;
		s1++;
		s2--;
	}
}


//第二种(逆序遍历数组)
void Reverse1(char *str,char *ptr)
{
	int n = strlen(str);
	printf("字符串长度为%d\n", n);
	int i = 0;
	for (i = 0; i <= n-1; i++)
	{
		ptr[i] = str[n - i - 1];
	}
	ptr[i] = '\0';
}

//第三种(与第一种方法本质相同,都是第i个与第n-i-1个数交换)
void Reverse2(char *str)
{
	int n = strlen(str);
	int i;
	char temp;
	for (i = 0; i < n / 2; i++)
	{
		temp = str[i];
		str[i] = str[n - i - 1];
		str[n - i - 1] = temp;
	}
}
*/

32.字符串常量与字符数组

/*
#include <stdio.h>
#include <string.h>
int main() {
    char str[] = "http://c.biancheng.net";
    int len = strlen(str), i;
    //直接输出字符串
    printf("%s\n", str);
    //每次输出一个字符
    for (i = 0; i < len; i++) {
        printf("%c", str[i]);
    }
    printf("\n");
    return 0;
}


运行结果:
http ://c.biancheng.net
http://c.biancheng.net

字符数组归根结底还是一个数组,上节讲到的关于指针和数组的规则同样也适用于字符数组。更改上面的代码,使用指针的方式来输出字符串:
*/



/*
#include <stdio.h>
#include <string.h>
int main() {
    char str[] = "http://c.biancheng.net";
    char* pstr = str;
    int len = strlen(str), i;
    //使用*(pstr+i)
    for (i = 0; i < len; i++) {
        printf("%c", *(pstr + i));
    }
    printf("\n");
    //使用pstr[i]
    for (i = 0; i < len; i++) {
        printf("%c", pstr[i]);
    }
    printf("\n");
    //使用*(str+i)
    for (i = 0; i < len; i++) {
        printf("%c", *(str + i));
    }
    printf("\n");
    return 0;
}


运行结果:
http ://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net


*/



/*
除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串,例如:

char* str = "http://c.biancheng.net";

或者:

char* str;
str = "http://c.biancheng.net";



字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符的地址;我们通常将第 0  个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char * 。

下面的例子演示了如何输出这种字符串:


#include <stdio.h>
#include <string.h>
int main() {
    char* str = "http://c.biancheng.net";
    int len = strlen(str), i;

    //直接输出字符串
    printf("%s\n", str);
    //使用*(str+i)
    for (i = 0; i < len; i++) {
        printf("%c", *(str + i));
    }
    printf("\n");
    //使用str[i]
    for (i = 0; i < len; i++) {
        printf("%c", str[i]);
    }
    printf("\n");
    return 0;
}



运行结果:
http ://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net

*/


/*

这一切看起来和字符数组是多么地相似,它们都可以使用% s输出整个字符串,都可以使用* 或[]获取单个字符,这两种表示字符串的方式是不是就没有区别了呢?

有!它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。

内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。

我们将第二种形式的字符串称为字符串常量,意思很明显,常量只能读取不能写入。请看下面的演示:


#include <stdio.h>
int main() {
    char* str = "Hello World!";
    str = "I love C!";  //正确
    str[3] = 'P';  //错误
    return 0;
}



这段代码能够正常编译和链接,但在运行时会出现段错误(Segment Fault)或者写入位置错误。

第4行代码是正确的,可以更改指针变量本身的指向;第5行代码是错误的,不能修改字符串中的字符。

*/



/*
到底使用字符数组还是字符串常量

在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操作,那么只能使用字符数组,不能使用字符串常量。

获取用户输入的字符串就是一个典型的写入操作,只能使用字符数组,不能使用字符串常量,请看下面的代码:


#include <stdio.h>
int main() {
    char str[30];
    gets_s(str);
    printf("%s\n", str);
    return 0;
}



运行结果:
C C++ Java Python JavaScript
C C++ Java Python JavaScript
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值