C语言自己总结

C语言小白,以下内容只是自己做个记录,方便查看。

1.指针加1之后会指向下一个数据的位置,并不会是地址加1。输出aa 为b的值,20。在memcpy(void *des, void *src , int len);

地址做偏移只根据数据个数进行偏移即可。

指针只是一个地址,占用几个字节,与C语言和定义的类型无关,与系统位数有关,比如32位系统占用4个字节,我的64位系统占用8个字节。

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int a=10, b=20,c=30;
int *aa,*bb,*cc;
aa= &a;
bb = &b;
cc = &c;
int len = bb-aa;
printf("aa:%d\n",*(aa+1));
printf("bb:%d\n",sizeof(aa));
 }

2.关于参数的传递方式,主要分为值传递和地址传递。主要明白每一个定义的变量会分配一个内存,如果要修改变量的值可以直接操作变量或者操作地址。但是如果需要通过外部函数(调用)来修改变量的值就只能通过地址的方式来进行。由于数组元素的地址的本质仍然为地址,所以属于地址传递方式.比如定义 int a[10];则a即为数组的首地址,也就是数组a[1]的地址。

值传递:

形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

地址传递:

形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

指针传递的实质:

指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,

即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的

任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值(这里是在说实参指针本身的地址值不会变)

(1)我们期望的ab的值会交换,但是并没有达到预期的效果,因为这个函数使用的是值传递。如果需要通过调用函数来修改ab的值,只能操作ab地址的方式来修改。

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

void swap(int x ,int y)
{
int temp;
temp= x;
x = y;
y =temp;  
printf("x=%d,y=%d\n",x,y);
}

int main()
{
  int a=10, b=20,c=30;
  if(a<b)
   {
   swap(a,b);
   printf("a=%d,b=%d\n",a,b);
  }
}

输出:

x=20,y=10
a=10,b=20
(2)值还是没有进行改变,但是通过操作指针达到了交换a和b地址。

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

void swap(int *x ,int *y)
{
int *temp;
temp= x;
x = y;
y =temp;  
printf("x=%d,y=%d\n",*x,*y);
}

int main()
{
  int a=10, b=20;
  int *aa= &a;
  int *bb = &b;
  
  if(a<b)
   {
   swap(aa,bb);
   printf("a=%d,b=%d\n",a,b);
  }
}

输出:

x=20,y=10
a=10,b=20

(3)这个通过传递地址,修改了对应地址的内容。

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

void swap(int *x ,int *y)
{
int temp;
temp= *x;
*x = *y;
*y = temp;  
printf("x=%d,y=%d\n",*x,*y);
}

int main()
{
  int a=10, b=20;
  int *aa= &a;
  int *bb = &b;
  
  if(a<b)
   {
   swap(aa,bb);
   printf("a=%d,b=%d\n",a,b);
  }
}

输出:

x=20,y=10

a=20,b=10

3 数组指针。C语言规定数组名代表数组的首地址,也就是第0号元素的地址。a+1则指向数组的第二个元素a[2]

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

int main()
{
  int a[10]= {22,24,34,45,56,68};
  printf("a addr:%p\n", a);
  printf("a1:%d\n", *a);
  printf("a1:%d\n", *(a+1));
}

输出:

a addr:0x7fff1fd2ef70
a1:22
a1:24

4. .h头文件的写法和定义

.h文件是头文件,内含函数声明、宏定义、结构体定义等内容

因为 #include "xx.h" 这个宏其实际意思就是把当前这一行删掉,把 xx.h 中的内容原封不动的插入在当前行的位置。所以.h的文件名不是必须与.c的文件名一致。

C语言要求使用之前必须声明,而include同名.h一般会放在.c的开头,因为如果.c中的函数也需要调用同个.c中的其它函数,那么这个.c往往会include同名的.h,这样就不需要为声明和调用顺序而发愁了.

而且,比方说 我在a.h里定义了一个函数的声明,然后我在a.h的同一个目录下建立a.c ,a.c里定义了这个函数的实现,然后是在main函数所在.c文件里#include这个a.h 然后我就可以使用这个函数了。 main在运行时就会找到这个定义了这个函数的a.c文件。必须要表明路径。

假定编译程序编译myproj.c(其中含main())时,发现它include了mylib.h(其中声明了函数void test()),那么此时编译器将按照事先设定的路径(Include路径列表及代码文件所在的路径)查找与之同名的实现文件(扩展名为.cpp或.c,此例中为mylib.c),如果找到该文件,并在其中找到该函数(此例中为void test())的实现代码,则继续编译;如果在指定目录找不到实现文件,或者在该文件及后续的各include文件中未找到实现代码,则返回一个编译错误.其实include的过程完全可以"看成"是一个文件拼接的过程,将声明和实现分别写在头文件及C文件中,或者将二者同时写在头文件中,理论上没有本质的区别。

.h文件的书写过程:不需要再重复添加库文件。

H文件中一般是声明,包括:变量声明、宏定义、枚举声明、结构体声明、函数声明等

1.在当前目录下创建一个.h文件,名字随意取,比如common.h。

2.#ifndef  _COMMON_H//和文件名一致,将.换成_  在文件开头添加_   //条件编译

#define _COMMON_H   //宏定义

#include "include/sqlite3.h" //其他函数的头文件

struct data

{

int adc;

union mpu6050_data env_all;

};//结构体定义

int create_table(void);//函数名

#endif

5头文件中的被重复引用。

头文件中的#ifdef/#define/#endif防止该头文件被重复引用。

但是是否能理解“被重复引用”是什么意思?是不能在不同的两个文件中使用include来包含这个头文件吗?如果头文件被重复引用了,会产生什么后果?是不是所有的头文件中都要加入#ifndef/#define/#endif 这些代码?

    其实“被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。比如:存在a.h文件#include "c.h"而此时b.cpp文件导入了#include "a.h" 和#include "c.h"此时就会造成c.h重复引用。

头文件被重复引用引起的后果:

有些头文件重复引用只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些,但是对于大工程而言编译效率低下那将是一件多么痛苦的事情。
有些头文件重复包含,会引起错误,比如在头文件中定义了全局变量(虽然这种方式不被推荐,但确实是C规范允许的)这种会引起重复定义

是不是所有的头文件中都要加入#ifndef/#define/#endif 这些代码?

    答案:不是一定要加,但是不管怎样,用#ifnde xxx #define xxx #endif或者其他方式避免头文件重复包含,只有好处没有坏处。个人觉得培养一个好的编程习惯是学习编程的一个重要分支。

下面给一个#ifndef/#define/#endif的格式:

    #ifndef A_H意思是"if not define a.h"  如果不存在a.h

    接着的语句应该#define A_H  就引入a.h

    最后一句应该写#endif   否则不需要引入

如果之前引用过a.h则会定义过A_H ,则将不会再执行后面的语句。

6.条件编译

一般情况下,源程序中的所有行均需要进行编译,但有时希望部分行在满足一定条件下才进行编译,即对部分内容指定编译的条件,这就是条件编译。以下宏指令可以实现条件编译:

#if    表达式

#ifdef  标识符

#ifndef   标识符

#else

#endif

条件编译命令最常见的形式为: 
    #ifdef 标识符 
    程序段1 
    #else 
    程序段2 
    #endif 
     

    它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。 这里的“程序段”可以是语句组,也可以是命令行。

常用的

#ifndef 标识符

程序段 1

#else

程序段 2

#endif

如果#ifndef指令所指定的标识符没有被#define定义过,则编译程序段1;否则,则编译程序段2。

6.空指针

int *p;

p = NULL;

空指针就是指针变量P不指向任何变量,也就是指针变量P不存储任何内容。第一次只是初始化并没有赋值,是个野指针,它的缺省值是随机的,它会乱指一气。它有可能指向的是一个空白的内存区域,可能指向的是已经受保护的区域,甚至可能指向系统的关键内存,如果是那样就糟 了,也许我们后面不小心对指针进行操作就有可能让系统出现紊乱,死机了。当初始化之后会随意指向一个地址,当赋NULL之后,  nil是一个对象指针为空。我们常见的空指针一般指向 0 地址,即空指针的内部用全 0 来表示(zero null pointer,零空指针)

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

void main()
{
  int a= 10;
   int *p;
   printf("a addr:%p\n", p);
   p = NULL;
   printf("a addr:%p\n", p);
   printf("p addr:%p\n",&p);
}

输出为:

a addr:0x7ffd3d296a80
a addr:(nil)
p addr:0x7ffd3d296990

7.野指针

野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

(1)成因:

指针变量未初始化

任何 指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。如果没有初始化,编译器会报错“ ‘point’ may be uninitializedin the function ”。
char *p; //此时p为野指针

指针释放后之后未置空

有时 指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
int *p;
p = malloc(1024);
free(p);//p重新变为空指针

指针操作超出了变量的使用范围

int *p;

p= malloc(5);
printf("%d\n",*(p+5));

(2)危险:

如果程序定义了一个指针,就必须要立即让它指向一个我们设定的空间或者把它设为NULL,如果没有这么做,那么这个指针里的内容是不可预知的,即不知道 它指向内存中的哪个空间(即野指针),它有可能指向的是一个空白的内存区域,可能指向的是已经受保护的区域,甚至可能指向系统的关键内存,如果是那样就糟 了,也许我们后面不小心对指针进行操作就有可能让系统出现紊乱,死机了。如果发生冲突就会崩溃。  

   如果野指针指向了之前没有被清空的地址区域,在之后操作该指针的时候就会出现错误。

(3)规避:

初始化时置 NULL

指针变量一定要 初始化为NULL,因为任何指针变量(除了static修饰的指针变量)刚被创建时不会自动成为NULL指针,它的 缺省值是随机的。

释放时置 NULL

free和delete只是把指针所指的内存给释放掉,但并没有把指针本身干掉。指针p被free以后其地址仍然不变(非NULL),只是该地址对应 的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。用free或delete释放了内存之后,就应立即将指 针设置为NULL,防止产生“野指针”。内存被释放了,并不表示指针会消亡或者成了NULL指针。(而且,指针消亡了,也并不表示它所指的内存会被自动释放)通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。例如:
   if(chunk != NULL)
   {
      chunk = NULL;

   }




猜你喜欢

转载自blog.csdn.net/qq_39759656/article/details/80954819