c语言提高

本文详细介绍了C/C++中的基本数据类型,如整型、指针和数组,特别强调了指针的使用,包括指针自增、指针数组、二维数组、行指针和函数指针。还讨论了字符串处理,如字符串数组、字符串指针、字符串拼接和字符串函数。此外,文章还涵盖了动态内存分配、文件操作以及函数参数传递等内容。

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

C/C++语法

数据类型

  1. 数据类型的实质是指向一定大小的内存空间的别名
int b[23];
//b是数组首元素的地址,而只有使用sizeof(b)或者&b时,b才代表整个数组的地址
  1. 数据类型的封装
    void字面意思是无类型,void*则为无类型指针,即可以指向所有类型的指针,malloc就是典型的例子,大小经过测试,是4个字节。
  2. 函数本质上也是一种数据类型,因为满足变量的三定义,名称,返回值,参数,所以有函数指针。格式为返回值类型 ( * 指针变量名) ([形参列表]);
int function(int x);
int (*f)(int x);//函数指针
f = function;

变量

  1. 既能读又能写的内存对象,不可以修改的叫常量,比如数组名就是常量,但是java中不是常量,因为是面向对象语言,所以数组也是一个对象,而对象就是变量。
  2. 变量相当于门牌号,读写数据是向变量名所指的内存空间读写,而不是在变量名里读写,和指针有点类似,实际上变量名在编译器中就是和指针进行映射,从而操作指向的内存空间。
  3. 变量名和内存的关系(从这里转载)

assert(断言)

  1. assert是宏定义,其作用是类似if else,当条件满足时才会继续运行,否则终止程序运行,避免了if语句冗长的表达式,其结构为assert(int expression),expression是表达式,当表达式返回值为0时结束,但是频繁调用assert会影响性能
  2. 可以通过在包含include的语句前插入#include NDEBUG来禁用
#include
#define NDEBUG
#include

const

int const a;
const int a;
//以上两者没有区别

const char *c;
char * const d;
//c是一个指向常整型的指针(指向的内存空间不可修改,指针自身可修改)
//d是一个常指针,(指针自身不能修改,但是指针指向的内存空间可以修改)
const char * const e;
//e是一个指向常整型的常指针(指针自身和指针指向的内存空间都不能修改)
//就是CONST右边的变量自身是不能变的
  1. c语言中的const是一个冒牌货,在编译时const确实是不能修改,不然报错,但是可以通过指针修改const的值
int const a = 9;
	/*a = 10;*/
	int* p = NULL;
	p = &a;
	*p = 10;
	printf("%d", *p);
	printf("%d", a);

宏定义(#define)

  1. 宏定义的类型是字符串,如果像转为其他格式需要提前转好
#define N ((int)4)//int型的数据
#define N ((char)4)//char型的数据
  1. 关于define和typedef的区别,不需要深入研究,毕竟不靠c吃饭,只需知道基本的区别就行,区别在于在C或C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像:
typedef    int       INT;
typedef    int       ARRAY[10];
typedef   (int*)   pINT;

#define为一宏定义语句,通常用它来定义常量(包括无参量与带参量)以及用来实现那些“表面似和善、背后一长串”的宏,它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了,但也因此难以发现潜在的错误及其它代码维护问题,它的实例像:

#define   INT             int
#define   TRUE         1
#define   Add(a,b)     ((a)+(b));
#define   Loop_10    for (int i=0; i<10; i++)

结构体

  1. 结构体之间的复制尽量使用手动复制而不是系统提供的p1=p2,因为当两个结构体都是在栈上时,p1=p2会将p2的内容全部拷贝至p1,但是如果结构体中有指针类型的数据例如Char *name,那么创建结构体时必然在堆上开辟空间,这时两个结构体的复制就是将p2.name的地址拷贝一份到p1.name,在最后的释放资源步骤中,释放完p2.name之后释放p1.name时就会报错,甚至泄露。解决方法是strcpy(p1.name,p2.name),之前工程实践深有体会。其实就是引用和复制。
  2. 结构体取地址是代表整个结构体的地址,这点和数组不同,数组名就代表首地址,取&代表整个数组,结构体名字就是结构体
  3. 判断结构体走到末尾,用一个结构体指针指向末尾,当指针大于等于末尾指针时表示为到结束,这就是流行的qp法,用q指向头部,p指向尾部,头大于尾部就表明未到结尾,但是只适用使用顺序结构存储的

一些退出用法

  1. 推出数值代表含义
  • exit(0):正常运行程序并退出程序;
  • exit(1):非正常运行导致退出程序;
  • return():返回函数,若在主函数中,则会退出函数并返回一值。
  1. 详细来说就是
  • return返回函数值,是关键字; exit 是一个函数。
  • return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
  • return是函数的退出(返回);exit是进程的退出。
  • return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
  • return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出, 非0 为非正常退出。
  • 非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。
  1. exit(0)与exit(1)对你的程序来说,没有区别。对使用你的程序的人或者程序来说,区别可就大了。一般来说,exit 0 可以告知你的程序的使用者:你的程序是正常结束的。如果 exit 非 0 值,那么你的程序的使用者通常会认为你的程序产生了一个错误。
  2. 以 shell 为例,在 shell 中调用完你的程序之后,用 echo $? 命令就可以看到你的程序的 exit 值。在 shell 脚本中,通常会根据上一个命令的 $? 值来进行一些流程控制。

->和.的区别

.的左边是一个表达式结果为结构体的表达式,而->的左边是指向结构体的指针

typedef struct Tree
{
  int date;
  struct Tree *length;
}Tree,*PTree;

Tree.date=a;//Tree为结构体,用.
PTree->date=b;//PTree为指针,用->

指针

如果定义了一个指针但是没有指向,要为指针分配空间才能存储数据,因为指针只是定义了,但是指向不知道,会出现segmentation fault

函数指针

  1. int (*p)(int,int);表示返回值为int,参数为两个int型的函数指针p。以函数指针作为形参,即可实现函数名作为传入参数,由另一个函数调用。
  2. 定义函数指针类型
使用typedef更直观更方便
// 定义一个原型为int Fun( int a );的函数指针
typedef int (*PTRFUN) ( int aPara );
typedef的功能是定义新的类型。这里定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,
这种函数以一个int为参数并返回char类型。后面就可以像使用int,char一样使用PTRFUN了。
```c
int add (int a,int b){
	return a+b;
}

typedef (*PTRFUN) (int aPara,int bPara);

int returnAdd (int *c,PTRFUN);//PTRFUN就是指有两个int参数类型且返回值为int的函数指针

int returnAdd (&C,&add);

行指针

行指针只有取*才是首地址,才能和char *等价
二级指针就相当于二维数组的数组名,取一次*是每行的首地址,再取一次*是每行具体的元素

int decode(char**s)
{
int pwd = 0;
for (int i = 0; i < 4; i++)
{
int cnt = 0;
for (int j = 0; j < strlen(s[i]); j++)
for (int k = 0; k < strlen(s[4]); k++)
if (s[i][j] == s[i][k]) cnt++;
pwd = pwd * 10 + cnt;
}
return pwd;
}

指针的递增

  1. *p++和(*p)++
*p++等价于*(p++),p肯定是个指针,这句话的意思是先取出*p的值,然后让指针指向下一个数据。
(*p)++的意思是先把*p的值取出来,然后把*p的值++。
使用中要注意到右++是先使用后++,所以可能会造成一些误解。下面举一个例子。

int a[4] = { 1,3,5,4 };
	 int arrp[2] = {1,3};
    int arrq[2] = {1,3};
    int *p = arrp;
    int *q = arrq;
    int a = *p++;//将arrp[1]的值给a,同时让p指向下一个元素
    int b = *p;//*p指向了arrp[2]
    int c = (*q)++;//将arrq[1]赋值给c,然后arrq[1]自增
    int d = *q;//将arrq[1]赋值给c,

    printf("%d,%d,%d,%d",a,b,c,d);
    return 0;

指针的使用

  1. 通过指针引用数组元素,
int a [5]={1,2,3,4,5};
int *p=&a;
(p+i)//第i个元素的地址
*(p+i)//第i个元素
  1. 数组名为第一个元素a[i]的地址,是一个指针常量,不能进行a++运算,
    要用a+1进行运算(递归注意,就是这里错的)。但是用int *p=a之后就可以用p++。
  2. 对数组进行操作的话,函数只要传入首个元素的地址就可以(数组是连续内存),再知道数组的长度,就可以对数组进行索引和一系列操作
#include <stdlib.h>
#include <stdio.h>

int max(int *a,int n);

double average(int* a, int n);

int main(int argc, char const *argv[])
{
     int n;
     scanf("%d",&n);
     int a[20];
     int x = max(a,n);
     double m = average(a,n);
     printf("max = %d,average =  %f",x,m);
	return 0;
}

int max(int *a,int n){
     int *p;//定义一个指针,指向a数组,这样可以执行p++操作(详见1)
     int max = *p;
     for(p=a;p<a+n;p++){  //n是数组长度,p向后一个不是二进制的+1,而是数组元素类型占用内存+1(这样就直观表现为数组元素向后+1)
     	if(*p>max){
     		max=*p;
     	}
     }

     return max;

}

double average(int* a, int n){
       itn *p;
       double sum = 0;
       for(p=a;p<a+n;p++){
       	sum+=(double)*p;
       }

       double average = sum/(double)n;

return average;
}
  1. 对字符串数组进行操作时,容易忘记最后加上一个中止符号(‘\0’),

行指针和二维数组

  1. 行指针的定义方式
//数据类型  (* 变量名)[每行的元素个数]
int (*p)[4]//每行元素个数为4个
  1. 访问具体元素只能用列指针访问,有三种形式
int a[3][4];
//数组下标访问
a[2][3];
//列指针访问
*(a[2]+3);
//行指针访问
*(*(a+2)+3)
  1. 编写子函数时,如果是二维数组,那么传入的参数就得是行指针,而不是普通指针
void  ArrayOutput (int (*p)[4],int n){
  ........
}
  1. 数组名是地址常量,用++操作是无法操作的
int a[]={2,3,4,3,3,3};
 for(i=0;i<10;i++,a++){
 prinf("%d",*a);//这段是错误的,因为a是地址常量,++无法改变地址,而a+i就可以访问数组其它元素
}

for(i=0;i<10;i++,a++){
 prinf("%d",*(a+i));//正确
}

字符数组和字符指针

区别:

  1. 字符数组有无穷个元素组成,每个元素存放一个字符,而字符指针中存放的是字符串首字符的地址,不是存放的字符串
  2. 可以对字符串指针赋值,而不能对数组名赋值
char *s ;
s="i love china!";//对
char a[13];
a[0]="i";//对
a="i love china";//错
  1. 编译时为字符串数组分配若干个存储单元,为字符指针分配一个存储单元
  2. 字符指针的值可以改变,而数组名是常量地址,无法更改
char *s;
s+=7;//对
char a[13];
a+=7//错
  1. 初始化并不等价
char str[13]="i love china";//正确,

char str[13];
str[]="i love china";//错误,因为一旦数组大小确定了之后,在接下来的引用中都必须带上下标,并且数组名是常量地址,无法赋值
char str*="i love china";

char str*;
str="i love china"//这是等价的,因为str*中存储的是地址
  1. 字符数组中的元素值是可以改变的,但是字符指针中的值是不能改变的,因为常量是不能改变的,数组中的并不是常量,而是复制在数组中的副本,但是指针中所指向的就是常量
char str[13]="i love china";
str[3]="e";

char *str="i love china";
*(str+2)="e";//错
  1. 可以用字符指针指向一个格式字符串,这样就可以减少输入麻烦
char *format;
format="%d %d";
printf(format,a,b);
  1. 指针数组
    指针数组中存放的是地址,适合用来处理与字符串相关的问题,比用字符数组更加灵活,并且字符串一般是长度不相等的,用指针比二维数组节省空间

指针与int相加

  1. 指针与int n相加,则表示指针p的指向向后移动了n个p指向的元素所占有的空间值,相当于向后移了n个元素
char a;
char* p;
p=&a;//假设p值为2000,那么p+4就是2004;
p=p+4;
int b;
int *p1;
p1=&b;//p值为2000,那么p+4就是2000+4*4=2016,因为int是4个字节
p1+=4;

二级指针做输入的三种内存模型(字符串二维数组)

  1. 用字符指针数组指向不同的字符串,交换的是指针的指向
char* myArray[] = { "aaaa","dsf","dsfafds","dsaf","dsafads" };//这是字符数组,而不是字符串,没有0
	//打印
	/*for (int i = 0; i < sizeof(myArray)/sizeof(myArray[0]); i++){
		printf("%s==", myArray[i]);
		printf("%s\n", *(myArray+i));
	}*/

	int count = sizeof(myArray) / sizeof(myArray[0]);

	char* temp = NULL;

	//排序
	for (int i = 0; i < count; i++) {
		for (int j = i; j < count; j++) {
			if (strcmp(myArray[i],myArray[j]>0)){
				//交换指针
				temp = myArray[i];
				myArray[i] = myArray[j];
				myArray[j] = temp;
			}
		}

	}
  1. 用二维数组存储多个字符串,交换的是内存块,其内存模型在人看来是二维的,但是计算机中没有二维内存的概念。二维数组在内存中仍然是一维的,例子:char [10][20],在内存中就是分配了200个字节的总空间,其中每20个字节就是一个大的部分,总共有10个大部分,数组中每个元素就是占20个字节的内存空间,数组指针加加就是每次跳过20个字节内存。
char myBuf[30];
	char myArray[10][30] = { "asfda","afdsda","dfasgfd","fdsgsz" };
	//打印
	int count = 4;
		for (int i = 0; i < count ; i++){
			printf("%s\n",myArray[i]);
		}
//这里交换的及时mybuf[30]这个内存区
  1. 建立字符指针数组,为数组每个元素分配空间,构成第3种内存模型
  • 指针也是一种数据类型,数据类型的实质就是一定大小的内存空间,那么指针就一定是一种数据类型,他的内存大小经过编译器计算是4个字节(字符指针和int指针都是4个字节)
    在这里插入图片描述
int  i = 0;
	char** p2 = NULL;//二级指针
	int num = 5;
	//给二级指针分配内存空间,每个内存空间里面存放者一个字符指针,就相当于字符指针数组
	p2 = (char**)malloc(sizeof(char*)*num);
	//printf("%d", sizeof(p2));

	for (int i = 0; i < num; i++)
	{
		p2[i] = (char*)malloc(sizeof(char) * 100);//给每个字符指针分配100个字节的空间
		sprintf(p2[i], "%d%d%d", i + 1, i + 1, i = 1);
	}

	for (int i = 0; i < num; i++)
	{
		printf("%s\n", p2[i]);
	}

	//释放内存,根据先申请后释放原则,就是栈
	for (int i = 0; i < num; i++)
	{
		if (p2[i]!=NULL)//释放字符串的空间
		{
			free(p2[i]);
			p2[i] = NULL;
		}
		if (p2!=NULL)//释放字符指针的空间
		{
			free(p2);
		}
	}

指向函数的指针

  1. 定义:类型名 (*变量名)(参数表列)
int (*p)(int,int);
  1. 通过指针变量来访问函数
int (*p)(int,int);
p=max;//不能再其后加上参数列表,ex:p=max(*P)(a,b);
c=(*P)(a,b);

int max(int z,int c){
 //交换两数
}
  1. 函数指针进行自加和+n都是无意义的
  2. 函数指针可以作为参数传入函数,这样在函数中就可以调用实参函数

各种指针的区分

int i;//整数
int *P;//指针指向整型
int a[5];//数组
int *p[4];//行指针,每行4个元素
int (*P)[4];//指针数组,元素类型为指针
int f();//函数
int *P();//返回值是整型指针的函数
int (*P)();//指向函数的指针
int **P;//二级指针,存放的是指针的地址
void *P;//只返回地址不返回数据类型的指针

p++和P+i的区别

  1. p++操作完后指针已经移位,要想重新输出,就要另设新指针,令新指针指向数组首地址才能继续输出,否则输出的是随机数,容易忘记,所以用p+i更加保险
  2. 数组名相当于指针常量,是不能像指针一样变动的,所以将数组名更改后数组的任然是原数组,不会变

输入输出

  1. 以某一个特定字符作为结束标记
while(1){
		scanf("%d",&La.data[i++]);
		La.length++;
		ch=getchar();//因为scanf是不会读取末尾的换行换行标记的。
		if(ch=='\n'){//以换行作为结束标记
			break;
		}
	}
  1. sscanf和ssprintf,处理字符串问题的利器,使用具体用法可以自己查找
  2. 字符数组使用gets和scanf%s输入时会在数组末尾自动加加上结束符号就是\0,注意\0是ascii码是0即空字符null,只有char类型数组需要,int类型不需要。如果使用getchar等函数手动输入时,记得要在字符串末尾加上结束符号,否则会输出乱码。

链表

  1. 链表逆置,就想到头插法,栈
struct ListNode *reverse( struct ListNode *head )
{
    struct ListNode *L=(struct ListNode*)malloc(sizeof(struct ListNode)),*p,*q;//L就是新的头节点
    L->next=NULL;
    p=head;//遍历的是p,相当于中间变量
    while(p)
    {
        q=(struct ListNode*)malloc(sizeof(struct ListNode));
        q->data=p->data;
        q->next=L->next;//头插法
        L->next=q;//头插法  q,新节点
        p=p->next;
    }
    return L->next;
}

函数

c中常用函数函数

  1. 不区分大小写函数(stricmp)
  2. fflush(stdin)清空缓冲区
  3. getchar()从缓冲区中读取一个字符
  4. system(“CLS”)清空屏幕用
  5. atoi,将字符串转为数字
  6. iota将数字化为字符串

主函数的参数

  1. 主函数的参数
int argc, char const *argv[]

argc是控制台传入主函数的字符串个数,而argv是指针数组,本质是二级指针
2. 过程
首先切换到可执行程序所在目录(第一个字符),接着输入要传入主函数的参数
在这里插入图片描述

文件

  1. 文件使用之后一定要关闭,不然会留在缓冲区,如果进行多次读写操作时,就会出现无法读出写入操作
  2. 删除文本文件中指定内容
  • 创建临时文件,便读取边删除,调用system函数删除元文件,在将文件改为原来的名字
  • 将文件全部读入内存,删除指定内容,用w方式打开文件,在读写进去(不推荐,只适合空间小的文件,可能把内存撑爆)
  • 删除文件后不断调用fseek函数调动指针位置(不推荐,不停调用底层代码,效率低,并且算法是最复杂的)
    具体操作可以查阅资料,这里就不说了,还是那句话,不靠c吃饭。
  1. fscanf也是可以以转义符的格式读取数据的,这样读取一行的文本就不一定需要fgets来完成了。
fscanf(fp,"%s\n%s\t%s\n%s\t%s\n%c\n%s\n%s\n",pNew->question,pNew->option_A,pNew->option_B,pNew->option_C,pNew->option_D,&pNew->rightAnswear,pNew->difficulty,pNew->knowledge);
  1. c语言读取文本文件时,devc用的时ANSI编码,而我的记事本我把它改成默认utf-8的编码,因此会显示乱码,问题不大,只要点击文件->另存为->文件类型的下面有个编码,选择ANSI即可。(或者直接换ide,一步到位)

文件操作有关函数

  1. ftell() 函数用于得到文件位置指针当前位置相对于文件首的偏移字节数;
  2. B中fseek()函数用于设置文件指针的位置;
  3. C中rewind()函数用于将文件内部的位置指针重新指向一个流(数据流/文件)的开头;
  4. D中ferror()函数可以用于检查调用输入输出函数时出现的错误。
  5. 若读文件还未读到文件末尾, feof()函数的返回值是( 0),到尾返回-1,一般不适用eof判断是否末尾,应为eof要返回两次才能确定结束,
    用fgetc==NULL判断更方便
  6. 已知函数的调用形式fread(buffer, size, count, fp);其中buffer代表的是一个指针,指向要读入数据的存放地址。
  7. 字符串赋值结束后要加上结束标志0。
  8. 二进制文件现在一般使用的是dat后缀,但实际上不止这些,像jpg,avi等文件本质上也是一种二进制文件,不过由于需要特定的解码器打开,后缀名有所不同
  9. fscanf和fread返回值是读到的数据个数
  10. 如果文件名要从键盘输入的话,就得在fopen里面的文件地址不加双引号,加了就是字符数组名,而我们要传的是指针

文本文件与二进制文件的区别

  1. 文本文件本质上是一个二级制文件,在进行读写时,需要翻译,而二进制文件不需要,读写速度更快
  2. 文本文件已于理解,二进制不易阅读和理解
  3. 文本文件的利用率低,有的字符拥有过大的空间,占用内存大,二进制利用率高,占用内存少
  4. 不同系统对文本文件的转义字符操作有所不同,因此需要针对操作系统作对应的更改,移植性差,二进制文件直接可以读写,移植性高
  5. 文本文件对对应解码器的要求低,记事本基本上可以打开所有文本,但不同类型的二级制文件需要相应的解码器才能浏览

数组

  1. 数组名是数组首元素的地址,而&数组名是整个数组的地址
int c[200]={0};
//c是首元素的地址,c+1步长是4个字节
//&c是整个数组的地址,&c+1步长是200*4=800
  1. 二维数组的名称除了sizeof和对名称取地址外,就是一个指向第一个一维数组的指针,一维数组也是除了对数组名进行sizeof和取地址外,都指向数组中首元素的地址
/************二维数组*************/
	int a[3][5];
	int(*p)[5];
	p = a;
	int(*p1)[3][5];
	p1 = &a;
	printf("a=%d", a);//a就是数组首元素的地址
	printf("p=%d\n", p);
	printf("p+1=%d\n", p + 1);//每次加1就是加一个数组的内存空间,就是5*4=20
	printf("&a=%d", &a);//这个就是整个数组的地址,
	printf("p1=%d\n", p1);
	printf("p1+1=%d\n", p1 + 1);//加一就是加整个数组的步长,就是3*5*4=60个字节

	/*************一维数组************/
	int c[5];
	int* p2 = NULL;
	p2 = c;
	int(*p3)[5];
	printf("c=%d", c);
	printf("p2=%d", p2);
	printf("p2+1%d", p2 + 1);//就是整个数组,步长5*4=20
  1. 数组的下标可以为负数,但前提是当前指针已经偏移过,不然可能指向其他的随机地方
int a[5] = { 1,2,3,4,5 };
	int* p = a;
	p = a + 2;
	printf("%d", p[-1]);//a+2是第三个元素,就是3,-1就相当于把当前指针向前移,就是第二个元素2,如果没有使p移动,直接写
	//写负的下标,就会指向a的前面一个元素,是随机的。
  1. 数组名和地址详解
  2. c和c++中没有求数组长度的函数,但是有快捷方法,就是用数组长度/数组内元素长度
int len = sizeof(array)/sizeof(array[0])
  1. 数组名就是数组首元素地址,即a = &a[0]。但是注意,当数组作为参数传递时,本质上是传递的指针,而不再是整个数组的地址,也就是说用上面这种方法是拿不到作为参数传递的数组的长度的。最好直接将数组长度传递进去。
  2. 数组名不能自加仅限在同一函数中,传入函数时,函数规定的类型是指针类型,相当于将数组名的地复制了一份。传给了形参列表中的参数,因此可以对形参自加

动态内存分配

  1. 四种函数,malloc,calloc,realloc,free
  2. malloc函数(动态内存分配,链表创建的基础):其函数原型为void malloc(unsigned int size),size是输入数据的长度,返回类型是void类型,需要指定类型。
  Head = (node*)malloc(sizeof(node));//定义头节点
  //node是重命名后的结构体变量名(即相当与指定int*类型指针一样,sizeof()*n用来计算n个结构体的长度。
  1. realloc(再分配函数)
    用来冲新分配内存,原型是:(void*)realloc(void* ptr,size_t size);
    ptr是需要重新分配内存的指针所指向的空间,size是新的内存空间大小。
  • size可以比原来大或者小,当内存不够用时,就可以调用realloc来重新分配内存
  • 如果ptr指向NULL,那么作用和malloc是一样的,而size值为0时,ptr指向的空间就会被释放,类似free()。
  1. 注意点:
  • ptr必须是在动态内存分配成功的指针,像int *i,int a[2]这样的指针时不行的,即只有通过malloc,calloc,realloc分配成功的指针才能被再次分配内存
  • 再分配成功后,原来ptr指针就会被系统回收,一定不能对ptr指针做任何操作,包扩free,但是可以对realloc的返回值进行正常操作
  • 如果扩大内存,就会把数据复制到新地址(新地址可能与原地址相同也可能不同,单依旧不能对原地址进行任何操作),如果是缩小内存,原数据会被复制并截取新长度
  • 如果分配失败,ptr指向的内存不会被释放,内容也不会被改变,依然可以正常使用
#include <stdio.h>
#include <stdlib.h>
int main ()
{
    int input,n;
    int count = 0;
    int* numbers = NULL;
    int* more_numbers = NULL;
    do {
        printf ("Enter an integer value (0 to end): ");//输入0表示结束
        scanf ("%d", &input);
        count++;//每次递增,表示内存分配的空间,因为realloc是复制再重新分配,而不是在原地址直接增加内存,因此要递增
        more_numbers = (int*) realloc (numbers, count * sizeof(int));//分配count个空间
        if (more_numbers!=NULL) {//如果分配内存成功,那么more_numbers就非空
            numbers=more_numbers;//将numbers指向more_numbers
            numbers[count-1]=input;//调用内存分配的函数都是连续空间,因此类似数组,可以用下标来访问。只不过内存大小shi可以改变的,即动态数组
        }else {
            free (numbers);//失败就释放指针
            puts ("Error (re)allocating memory");
            exit (1);//非正常运行导致程序退出
        }
    } while(input!=0);
    printf ("Numbers entered: ");
    for (n=0;n<count;n++) printf("%d ",numbers[n]);
    free (numbers);
    system("pause");//暂停
    return 0;
}
  1. calloc
  • 原型:void* calloc(unsigned n,unsignedsize);n是要开辟的元素个数,size是字节大小
  • 与malloc的区别:calloc一般是用来给数组扩大空间的,并且会对分配的内存全部初始化为0,会降低程序的运行效率
//calloc相当于
p=malloc();
memset(p,0,size);
  1. free函数
  • 原型:void free(void *p);
    -作用是释放p所指向的内存空间,p应为最近一次调用内存分配的函数,无返回值

GetMemory

  1. 栈中分配局部变量空间,是系统自动分配空间。定义一个 char a;系统会自动在栈上为其开辟空间。由于栈上的空间是自动分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。
    堆区分配程序员申请的内存空间,堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。
    静态区是分配静态变量,全局变量空间的。
    在这里插入图片描述

在这里插入图片描述

分析:
毛病出在函数GetMemory1 中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory1就会泄露一块内存,因为没有用free释放内存。Test1中调用GetMemory1时,函数参数为str的副本不是str本身

在这里插入图片描述

结果:输出hello
分析:动态分配的内存不会自动释放;
没有测试是否成功分配了内存,应该有if (*p == NULL) { ……} 之类的语句处理内存分配失败的其情况。
在这里插入图片描述

结果:输出乱码。
分析:字符数组p存在于栈空间,是局部变量,函数返回后,内存空间被释放,因此输出无效值。字符数组的值是可以修改的,例如p[0] = 't‘。

在这里插入图片描述

结果:输出hello
分析:p指向的是字符串常量,字符串常量保存在只读的数据段,是全局区域,但不是像全局变量那样保存在普通数据段(静态存储区)。无法对p所指的内存的内容修改,例如p[0] = 'y;这样的修改是错误的。
在这里插入图片描述

结果:输出hello
分析:直接返回常量区。
在这里插入图片描述

结果:能够输出world,但程序存在问题。
分析:程序出现了野指针。
野指针只会出现在像C和C++这种没有自动内存垃圾回收功能的高级语言中, 所以java或c#肯定不会有野指针的概念. 当我们用malloc为一个指针分配一个空间后, 用完这个指针,把它free掉,但是没有让这个指针指向NULL或某一个特定的空间。如上面程序一样,将str进行free后,只是释放了指针所指的内存,但指针并没有释放掉,此时指针所指的是垃圾内存;这样的话,if语句永为真,if判断无效。delete也存在同样的问题。

防止产生野指针:(1)指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。(2)当free或delete后,将指针指向NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。

  1. memset:将数组中的元素统一赋初值,注意使用memset使用时要加上string.h头文件,并且推荐初始值赋0或-1,因为memset是按字节进行赋值,int是4字节,都赋值0或-1结果不会出错,如果想赋值为其他数字,使用fill函数

字符串

和string有关的函数

  1. 使用scanf读取string,看这里
  2. resize()和c_str(),将string赋值给字符数组,首先resize(),调整容器的大小,string本质上也是一种容器,因此可以重新赋值。要想在printf中输出string,就要使用c_str()将string转换为char[]
  3. 使用string,引入头文件,注意和<string.h>区分,两者是不一样的
  4. string只能用cin和cout输入输出
  5. ans.size()计算字符串的长度,此外字符串可以用index索引取得对应字符
  6. ans.erase()相当于remove,参数是iterator,可以用ans.begin()和ans.end()来获得.
  7. lowwer_bound,是查找第一个大于等于val的位置,upper_bound是查找第一个大于val的位置,两者的实现基于二分法,查找第一个满足某种条件的元素.
  8. 进制转换分为字符串形式的转换10进制和数字形式,两者的区别主要是字符串是从前向后的,数字是从后向前的
//这是字符串形式的 
int len = strlen(a);
	
	LL ans=0;
	
	for(int i=0;i<len;++i){
		ans=ans*radix+map[a[i]];
	}
	
	if(ans<0||ans>t){
		return -1;
	}
	return ans;

//这是数字形式的

int product=1;
int radix;

int ans=0;

while(x!=0){

  ans+=(x%10)*product;
  x/=10;
product*=radix;
}

scanf与gets的区别

  1. gets的分割符只有回车,而scanf的分隔符是所以空白字符(空格,制表符,回车),因此gets能够读入空格,scanf不能。
char str[23];
gets(str);//输入hello world,会原样输出,
scanf("%s",str);//scanf遇到空格会停止读入,输出hello	
  1. 对待buffer(缓冲区)里的空白字符不同:scanf在读取非空白字符之前会忽略掉空白字符(被忽略的空白字符会被scanf移走),当读取结束后,会将分割符留在缓冲区中,而gets遇到回车就会结束输出,并将回车从缓冲区中移走,例如输入”\nhello\n”,scanf会先忽略\n,读入hello,遇到\n输入完成,将\n留在缓冲区,这是缓冲区里什么也没有,而gets遇到第一个\n就会停止输入,这时缓冲区里只剩hello\n,gets读入的是一个空串,即首字符为’\0’的字符串
  2. gets读走分隔符后会用’\0’来代替分隔符
  3. 在程序中有时会遇到scanf,gets连用的,就会出现scanf留在缓冲区的空白字符是\n,导致gets只能读入空白字符串的情况,这时就可以用scanf(“%s”,str)来读走最后一个空表字符前的所有空表字符,再用getchar读入一个空白字符就可以解决问题
  4. 用gets读取是无上限的,但要保证缓冲区足够大,才不会发生内存溢出

字符串数组

  1. 用二维数组定义。
int main(int argc, char *argv[]) {
char str1[3][20];
int i;
for(i=0;i<3;i++){
	scanf("%s",str1[i]);
}
printf("%s",str1[1]);  //输出第二个字符串
	return 0;
}
  1. 字符串数组第一个[]存储的是一位字符数组的地址,如果想获取具体某一个字符的话,就要加上列指针
char str[3][23];
str[2];//指向最后一行
str[2][0];//指向最后一行第一个字符
  1. 二维数组名实际上是一个二级指针,而字符指针只是一个指向字符串的指针
  2. 详细的解说可以去看c语言提高中的二级指针做输入的三种内存模型(字符串二维数组)

字符串函数

  1. puts函数:函数中可以加入转义符,且在输出结束字符串后会自动转行。char str[]={"hello\nworld!"};
  2. strcat函数:strcat(str1,str2);是将两个字符串连接起来,返回值是字符串数组1的地址,并且注意字符串1的长度必须足够大。
  3. strcpy:字符串复制函数strcpy(str1,str2)若是采用数组元素一个个赋值不方便,直接用函数赋值。strcpy可以是数组名复制,也可以是字符串常量strcpy(str1,“china!”);
  4. strcmp:字符串比较函数(strcmp(str1,str2)),若str1<str2,返回值小于0,若str1>str2,返回值大于0.若str1==str2,则返回0(用来判断字符串的结束标志)。

字符串数组与函数名做参

  1. 当要通过某个特定值来确定函数是否结束时,用strcmp0来判断,而不用srt[i]’#’或者str[i][j]=’#’来判断,即使特 定符号已经出现程序也会直接跳过(具体原因不知,有空再说)。
  2. 数组名做参:定义函数时,如果定义二维数组,那么列的值不能为空。void fet (int han[][20]){}
  • 注意形参要带变量类型,但是调用函数时不需要,尤其是字符串数组,只要带入数组名即可不需要包括后面[]的大小。fet(han);
  1. strcmp中,字符串数组和常量比较,常量字符串是用双引号,单个字符是用单引号

删除字符串中指定类型(由删除字符串中数字衍生而来)

  1. 通过循环将后面一个元素一直赋给前一个元素(小技巧,非数字可以用<0or>9)
#include <stdio.h>
#include <stdlib.h>

void del (char *str);

int main(int argc, char const *argv[])
{
	char str[20];
	gets(str);
    del(str);
    puts(str);
	return 0;
}

void del (char *str){
      int i=0,j;
      for (i=0;*(str+i)!='\0';i++)
      {
      	if((*(str+i)>='0')&&(*(str+i)<='9')){
      		for (j=i;*(str+j+1)!='\0';j++)
      		{
      			*(str+j)=*(str+j+1);	
      		}
      		*(str+j)='\0';//每次向前移,就将结束标志前移 
      		i=0;//不用双重循环就得将i置0,从头开始继续判断 
      	}
      }
     
}

或者

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

void deleteString(char *a,char str[15]);

int main(int argc, char *argv[]) {
char str[15];
gets(str); 
deleteString(str,str);
	return 0;
}

void deleteString(char *a,char str[15]){
	char *p;
	char *p1;
	int len=0;
	for(p=a;p<a+strlen(str);p++){
		if(!(65<=*p&&*p<=90||97<=*p&&*p<=122)){
		          for(p1=p;*p1!='\0';p1++){
		          	*p1=*(p1+1);
		          	 p--;
				  }
		   }
		      
	}
	
	for(p=a;p<a+strlen(str);p++){
		printf("%c",*p);
	}
}
  1. 将符合要求的字符存入对应的位置遇到符合条件的就可以按从头之尾的顺序输进原数组.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void del (char *str);

int main(int argc, char const *argv[])
{
	char str[23];
	gets(str);
	del(str);
	puts(str);
	return 0;
}

void del (char *str){
	int i,k=0;
     for (i=0;*(str+i)!='\0;i++)
     {
     	if (*(str+i)<'0'||*(str+i)>'9')//想要保留的字符
     	{
     		*(str+(k++))=*(str+i);//k每次自加
     	}
     }
     *(str+k)='\0';
}

  1. 建立新数组,将符合与不符合的分离,但是需要开辟新的数据空间

复制部分字符串

  1. 指针要移动到目标位置-1处,因为基数不同
  2. 要一对一的赋值,不能因为s和t传进来的是指针,而不是数组,s并不会向后自动输出直到’\0’。
  3. 记得加上结束标志
void strmcpy( char *t, int m, char *s )
{
    t+=m-1;
    while(*t)
        *s++=*t++;//要一步步赋值,
    //因为s和t传进来的是指针,而不是数组,s并不会向后自动输出直到'\0'
    *s=0;//加上结束标志
}

键盘输入字符串,并将其地址存入指针数组中

char str[3][34];
char *p[3];
for (int i = 0; i < 3; i++)
{
	gets(str);
	p[i]=str[i];//关键就在这,str[i]实际上是二维数组每行的首地址,而
	//字符串正好就是按行地址输出的
}

字符串数组和指针数组区别

  • 数组法占用空间大,必须开辟可以容纳最大字符串长度的空间,会造从内存的浪费,而指针数组存放的是指针,空间占用小。
  • 数组法操作运行次数多,每次进行字符串的交换都要进入strcpy函数,耗费时间比指针法多,指针法只需要交换指针所指的值就可以
  • 字符数组做形参时,数组大小要标出
  • 指针数组,使用时需要进行初始化赋值,或者需要指向具体的内存单元后,才能作为函数实参进行调用
  • 指针数组作为函数形参时,数组大小可以不指定
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
	int N;
	scanf("%d",&N);
    char str[N][30],tmp[30];
    int i,j;
    for(i=0;i<N;i++){
    scanf("%s",&str[i]);
    }
    for(i=1;i<N;i++){
        for(j=0;j<N-i;j++){
            if(strcmp(str[j],str[j+1])>0){
                strcpy(tmp,str[j]);
                strcpy(str[j],str[j+1]);
                strcpy(str[j+1],tmp);
            }
        }
    }
    printf("After sorted:\n");
    for(i=0;i<N;i++){
        printf("%s\n",str[i]);
    }
	return 0;
}

  1. 指针数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void swap(char *p,char *p1);

int main(int argc, char const *argv[])
{
	int N,j,i;
	scanf("%d",&N);
	char str[N][30];
	for (i=0;i<N;i++){
		scanf("%s",str[i]);
	}
	char *p[N];
	for (i=0;i<N;i++){
		p[i]=str[i];
	}
	for (i=0;i<N-1;i++){
          for (j=0;j<N-i-1;j++){
          	if(strcmp(p[j],p[j+1])>0){
          		swap(p[j],p[j+1]);
          	}
          }
	}
	
	for(i=0;i<N;i++){
		puts(str[i]);
	}
	return 0;
}

void swap(char *p,char *p1){
	char *t=NULL;
	t=p;
	p=p1;
	p1=t;
}

使用命令行输主函数参数

  1. int main(int argc, char * argv[]){}
    argc 是命令行输入的参数字符串个数,argv是字符指针数组,元素按顺序指向命令行的参数
c.exe 1 + 2;
argc=4;
argv[0]='c.exe';
argv[1]='1';
argv[2]='+';
argv[3]='2';
  1. 使用命令行首先要生成可执行文件,然后通过dos命令转到可执行文件所在目录,再输入参数
//假设可执行文件在D:\test

d:
cd test
c.exe 1 + 2
  1. 字符串数组第一个[]存储的是一位字符数组的地址,如果想获取具体某一个字符的话,就要加上列指针
char str[3][23];
str[2];//指向最后一行
str[2][0];//指向最后一行第一个字符
//比如这里要判断是什么数学符号,就要用argv[2][0],而不是argv[2]
//因为字符数组形式和字符不等价
  1. 字符化为整型数
    用aoti函数化整数,aotf化浮点数
    iota将整数化为字符,fota将浮点数化为字符
    传入字符的指针即可
char *p='1023';
aoti(p);
//在主函数中,就是argv【1】和argv【3】,而不是*argv【1】

在printf函数中输出%

%是格式符,想要输出文本,就得用两个%%。printf("%d%%%d=%d\n",a,b,a%b);

给字符数组赋值

main()
{
char s[30];
strcpy(s, "Good News!"); /*给数组赋字符串*/
}

上面程序在编译时, 遇到char s[30]这条语句时, 编译程序会在内存的某处留
出连续30个字节的区域, 并将第一个字节的地址赋给s。当遇到strcpy( strcpy 为
Turbo C2.0的函数)时, 首先在目标文件的某处建立一个”Good News!/0” 的字符串。
其中/0表示字符串终止, 终止符是编译时自动加上的, 然后一个字符一个字符地复
制到s所指的内存区域。因此定义字符串数组时, 其元素个数至少应该比字符串的
长度多1。
注意:

  • 字符串数组不能用”=”直接赋值, 即s=”Good News!”是不合法的。所以应分清字符串数组和字符串指针的不同赋值方法。
  • 对于长字符串, Turbo C2.0允许使用下述方法:例如
main()
{
char s[100];
strcpy(s, "The writer would like to thank you for"
"your interest in his book. He hopes you"
"can get some helps from the book.");
}
  • 指针数组赋值
main()
{
char *f[2];
int *a[2];
f[0]="thank you"; /*给字符型数组指针变量赋值*/
f[1]="Good Morning";
*a[0]=1, *a[1]=-11; /*给整型数数组指针变量赋值*/

}

无论是静态,局部还是全局数组只有在定义时才能初始话,否则必须通过其它方法,比如循环操作实现。

int a[3];
static int b[3];
a[3] = {1, 2, 3};
b[3] = {1, 2, 3};
没有在定义时初始化都是错误的!

以下是转载:

学了这么多年的C语言,突然发现连字符串赋值都出错,真的很伤心。
char a[10];
怎么给这个数组赋值呢?
1、定义的时候直接用字符串赋值
char a[10]="hello";
注意:不能先定义再给它赋值,如char a[10]; a[10]="hello";这样是错误的!
2、对数组中字符逐个赋值
char a[10]={'h','e','l','l','o'};
3、利用strcpy
char a[10]; strcpy(a, "hello");
易错情况:
1char a[10]; a[10]="hello";//一个字符怎么能容纳一个字符串?况且a[10]也是不存在的!
2char a[10]; a="hello";//这种情况容易出现,a虽然是指针,但是它已经指向在堆栈中分配的10个字符空间,现在这个情况a又指向数据区中的hello常量,这里的指针a出现混乱,不允许!

还有:不能使用关系运算符“==”来比较两个字符串,只能用strcmp() 函数来处理。
C语言的运算符根本无法操作字符串。在C语言中把字符串当作数组来处理,因此,对字符串的限制方式和对数组的一样,特别是,它们都不能用C语言的运算符进行复制和比较操作。
直接尝试对字符串进行复制或比较操作会失败。例如,假定str1和str2有如下声明:
char str1[10], str2[10];
利用=运算符来把字符串复制到字符数组中是不可能的:
str1 = "abc";     /*** WRONG ***/
str2 = str1;       /*** WRONG ***/
C语言把这些语句解释为一个指针与另一个指针之间的(非法的)赋值运算。但是,使用=初始化字符数组是合法的:
char str1[10] = "abc";
这是因为在声明中,=不是赋值运算符。
试图使用关系运算符或判等运算符来比较字符串是合法的,但不会产生预期的结果:
if (str1==str2) ...    /*** WRONG ***/
这条语句把str1和str2作为指针来进行比较,而不是比较两个数组的内容。因为str1和str2有不同的地址,所以表达式str1 == str2的值一定为02. C语言 谭浩强课本讲的很清楚:
 符号常量 与普通变量的区别:
符号常量 不占用存储空间,所以就没有内地址 了哦。
大家都懂 C语言的基本语法,但是 很多细节问题,就只有不同深入学习才懂的。
c和指针还是c专家编程上讲过,const修饰的是一个只读变量,所以可以用&取得一个const变量的地址。

用指针指向字符串

  1. 在结构体中,不能用字符串指针来代替结构体中的变量名
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct student{
	int math,english,computer;
}Stu;

int main(){
	Stu s;
char *p="math";
char *p1="english";
char* p2="computer";
printf("%d",s.*p);//这样写法是错误的,就是这么规定的,想要达到根据不同情况选择不同的属性,完全可以用ifelse进行判断,这种写法的意思是在结构体中有个p指针,*p是取这个p指针的值,但是这个结构体中并没有p指针,所哟是错误的。
	return 0;
}
  1. 字符串指针所指向的是字符串常量,赋值后就不能进行更改
  2. 输出字符串时要用%s,用%c想输出单个字符会输出乱码
  3. 字符串指针是可以移动的,可以用移动的指针来指定输出的位置
char *p="sfjalsafs"
printf("%s\n",(p+3) );//从a开始输出
  1. 字符指针数组中存放的是字符串第一个字符的地址,而不是将字符串存放进去,字符指针也是
  2. 字符串的判断结束要用
while(*s)
while(*s!=0)
while(s)//错误,这个是判断字符指针是否存在。
  1. 以后处理多个字符串时,可以使用字符指针数组,而不是像以前一样使用二维数组
char* myArray[] = { "aaaa","dsf","dsfafds","dsaf","dsafads" };//这是字符数组,而不是字符串,没有0结束标记
	//打印
	for (int i = 0; i < sizeof(myArray)/sizeof(myArray[0]); i++){
		printf("%s==", myArray[i]);
		printf("%s\n", *(myArray+i));
	}

/*********但是注意的是,指针仅仅是一个指针,他是要指向一个内存空间的,如果没有指向或分配内存空间,那么是无法使用指针指向二维数组的***********就是是说,想要使用指针数组表示二维数组的时候,首先要将指针数组的元素指向二维数组中的一维数组**/

char * str [5];
char strs [5][56];
for(int i=0;i<5;++i){
	//这个操作就将指针指向了一维数组的内存空间,就可以对这个空间进行处理了。
	str[i] = strs[i];
}

字符串与数组

  1. c语言中没有String类型,只有字符数组,而当字符数组充当字符串时,编译器会自动在结尾加上’\0’作为结束标志
    但是如果只是简单的字符数组,是单个赋值的时候就不会再结尾添加结束标志
char s[] = {'a','b','c','d','e'};
char s1[]={"abcde"};
printf("sizeofs:%d\n",sizeof(s));//5
printf("lengthofs:%d\n",strlen(s));//5
printf("sizeofs1:%d\n",sizeof(s1));//6自动加了结束标志
printf("lengthofs:%d\n",strlen(s1));//5
  1. 字符串拼接的两种方法,strcat和sprintf,区别就是sprintf按照指定格式拼接,而stract直接拼接
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *firstName = "Theo";
    char *lastName = "Tsao";
    char *name = (char *) malloc(strlen(firstName) + strlen(lastName));
    strcpy(name, firstName);
    strcat(name, lastName);
    printf("%s\n", name);
    return 0;
}


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

int main() {
    char *firstName = "Theo";
    char *lastName = "Tsao";
    char *name = (char *) malloc(strlen(firstName) + strlen(lastName));
    sprintf(name, "%s%s", firstName, lastName);
    printf("%s\n", name);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值