精华内容
下载资源
问答
  • C语言和C++区别

    万次阅读 多人点赞 2018-08-02 00:12:28
    c语言虽说经常c++在一起被大家提起,但可千万不要以为它们是一个东西。现在我们常用的C语言是C89标准,C++是C++99标准。C89就是在1989年制定标准,如今...我们来介绍C语言和C++中那些不同地方。 1.函数...

    c语言虽说经常和c++在一起被大家提起,但可千万不要以为它们是一个东西。现在我们常用的C语言是C89标准,C++是C++99标准的。C89就是在1989年制定的标准,如今最新的是C11和C++11标准。根据不同的标准,它们的功能也会有所不同,但是越新的版本支持的编译器越少,所以本文在讨论的时候使用的C语言标准是C89,C++标准是C++99.我们来介绍C语言和C++中那些不同的地方。

    • 1.函数默认值

    在C++中我们在定义或声明一个函数的时候,有时会在形参中给它赋一个初始值作为不传参数时候的缺省值,例如:

    int FUN(int a = 10);

    代表没有传参调用的时候,自动给a赋一个10的初始值。然而这种操作在c89下是行不通的,在c语言下这么写就会报错。

    我们都知道,系统在调用任何一个函数的时候都有函数栈帧的开辟,如果函数有参数则需要压入实参。平常在我们人为给定实参的时候,是按照参数列表从右向左依次将参数通过

    mov  eax/ecx   dword ptr[ebp-4]       //假设是int数据

    指令传入寄存器,再通过push指令压入。现在我们已经给定了函数参数的默认值,那么在压实参的时候只需要一步push初始值即可。效率更高。

    另外需要注意的是,赋初始值必须从参数列表的右边开始赋值,从左边开始赋值将会出错:

    int sum1(int a = 10,int b);        //错误
    int sum2(int a,int b = 20);            //正确
    

    因为如果sum1的声明是正确的,那么我们调用的时候怎么调用?sum1( ,20)?很可惜这样属于语法错误,调用这么写既然不对那就当然不能这样赋初始值了。相反,sum2的调用:sum2(20);合情合理,没有任何问题。

    实际在写工程的时候,我们都习惯将函数的声明写在头文件中而非本文件里,然后在不同的文件中写出它们的定义。那么这种情况可以赋初始值吗?当然可以,不论是定义还是声明处,只要你遵守从右向左赋的规则就可以。甚至你还可以这样给初始值:

    int  fun(int a ,int b = 10);
    int  fun(int a = 20,int b);

    眼尖的同学看见了下面的那行代码大喊错误,因为先给左边赋值了!

    其实这样声明完全没有问题,两句声明是同一个函数(函数多次声明没有问题),第一句已经给b了一个初始值,运行到第二句时已经等价于int fun(int a = 20,int b = 10);了。但是注意,这两句的顺序不能反转,否则就是错误的。

     

    总结:C89标准的C语言不支持函数默认值,C++支持函数默认值,且需要遵循从右向左赋初始值。

     

    • 2.inline内联函数

    说到内联函数大家应当不陌生,它又是一个C89标准下C语言没有的函数。它的具体做法和宏非常相似,也是在调用处直接将代码展开,只不过宏它是在预编译阶段展开,而内联函数是在 编译阶段进行处理的。同时,宏作为预处理并不进行类型检查,而inline函数是要进行类型检查的,也就可以称作“更安全的宏”。

    内联函数和普通函数的区别:内联函数没有栈帧的开辟回退,一般我们直接把内联函数写在头文件中,include之后就可以使用,由于调用时直接代码展开所以我们根本不需要担心什么重定义的问题——它连符号都没有生成当然不会所谓重定义了。普通函数生成符号,内联函数不会生成符号。

    关于inline还需要注意的一点是,我们在使用它的时候往往是用来替换函数体非常小(1~5行代码)的函数的。这种情况下函数的堆栈开销相对函数体大小来说就非常大了,这种情况使用内联函数可以大大提高效率。相反如果是一个需要很多代码才能实现的函数,则不适合使用。一是此时函数堆栈调用开销与函数体相比已经是微不足道了,二是大量的代码直接展开的话会给调试带来很大的不便。三是如果代码体达到一个阈值,编译器会将它变成普通函数。

    同时,递归函数不能声明为inline函数。说到底inline只是对编译器的建议,最终能否成功也不一定。同时,我们平常生成的都是debug版本,在这个版本下inline是不起作用的。只有生成release版时才会起作用。

    总结:C89没有,在调用点直接展开,不生成符号,没有栈帧的开辟回退,仅在Release版本下生效。一般写在头文件中。

     

    • 3.函数重载

    C语言中产生函数符号的规则是根据名称产生,这也就注定了c语言不存在函数重载的概念。而C++生成函数符号则考虑了函数名、参数个数、参数类型。需要注意的是函数的返回值并不能作为函数重载的依据,也就是说int sum和double sum这两个函数是不能构成重载的!

    我们的函数重载也属于多态的一种,这就是所谓的静多态。

    静多态:函数重载,函数模板

    动多态(运行时的多态):继承中的多态(虚函数)。

    使用重载的时候需要注意作作用域问题:请看如下代码。

    #include <iostream>
    
    using namespace std;
    
    bool compare(int a,int b)
    {
        return a > b;
    }
    
    bool  compare(double a,double b)
    {
        return a > b;
    }
    
    int main()
    {
        //bool compare(int a,int b);
        compare(10,20);
        compare(10.5,20.5);
        return 0;
    }
    

    我在全局作用域定义了两个函数,它们由于参数类型不同可以构成重载,此时main函数中调用则可以正确的调用到各自的函数。

    但是请看main函数中被注释掉的一句代码。如果我将它放出来,则会提出警告:将double类型转换成int类型可能会丢失数据。

    这就意味着我们编译器针对下面两句调用都调用了参数类型int的compare。由此可见,编译器调用函数时优先在局部作用域搜索,若搜索成功则全部按照该函数的标准调用。若未搜索到才在全局作用域进行搜索。

     

    总结:C语言不存在函数重载,C++根据函数名参数个数参数类型判断重载,属于静多态,必须同一作用域下才叫重载。

     

    • 4.const

     

    这一部分非常重要。在我的另一篇博客“C语言的32个关键字”中对C语言中的const也有所讲解。当中提到了这么一个问题:C语言中被const修饰的变量不是常量,叫做常变量或者只读变量,这个常变量是无法当作数组下标的。然而在C++中const修饰的变量可以当作数组下标使用,成为了真正的常量。这就是C++对const的扩展。

    C语言中的const:被修饰后不能做左值,可以不初始化,但是之后没有机会再初始化。不可以当数组的下标,可以通过指针修改。简单来说,它和普通变量的区别只是不能做左值而已。其他地方都是一样的。

    C++中的const:真正的常量。定义的时候必须初始化,可以用作数组的下标。const在C++中的编译规则是替换(和宏很像),所以它被看作是真正的常量。也可以通过指针修改。需要注意的是,C++的指针有可能退化成C语言的指针。比如以下情况:

    int b = 20;
    const int a = b;

    这时候的a就只是一个普通的C语言的const常变量了,已经无法当数组的下标了。(引用了一个编译阶段不确定的值)

    const在生成符号时,是local符号。即在本文件中才可见。如果非要在别的文件中使用它的话,在文件头部声明:extern cosnt int data = 10;这样生成的符号就是global符号。

    总结:C中的const叫只读变量,只是无法做左值的变量;C++中的const是真正的常量,但也有可能退化成c语言的常量,默认生成local符号。

     

    • 5.引用

    说到引用,我们第一反应就是想到了他的兄弟:指针。引用从底层来说和指针就是同一个东西,但是在编译器中它的特性和指针完全不同。

        int a = 10;
    	int &b = a;
    	int *p = &a;
    
    	//b = 20;
    	//*p = 20;

    首先定义一个变量a = 10,然后我们分别定义一个引用b以及一个指针p指向a。我们来转到反汇编看看底层的实现:

    可以看到底层实现完全一致,取a的地址放入eax寄存器,再将eax中的值存入引用b/指针p的内存中。至此我们可以说(在底层)引用本质就是一个指针。

    了解了底层实现,我们回到编译器。我们看到对a的值的修改,指针p的做法是*p = 20;即进行解引用后替换值。底层实现:

    再来看看引用修改:

    我们看到修改a的值的方法也是一样的,也是解引用。只是我们在调用的时候有所不同:调用p时需要*p解引用,b则直接使用就可以。由此我们 推断出:引用在直接使用时是指针解引用。p直接使用则是它自己的地址。

    这样我们也了解了,我们给引用开辟的这块内存是根本访问不到的。如果直接用就直接解引用了。即使打印&b,输出的也是a的地址。

    在此附上将指针转为引用的小技巧:int *p = &a,我们将 引用符号移到左边 将 *替换即可:int &p = a。

    接下来看看如何创建数组的引用:

    int array[10] = {0};       //定义一个数组

    我们知道,array拿出来使用的话就是数组array的首元素地址。即是int *类型。

    那么&array是什么意思呢?int **类型,用来指向array[0]地址的一个地址吗?不要想当然了,&array是整个数组类型。

    那么要定义一个数组引用,按照上面的小诀窍,先来写写数组指针吧:

    int (*q) [10] = &array;

    将右侧的&对左边的*进行覆盖:

    int (&q)[10] = array;

    测试sizeof(q) = 10。我们成功创建了数组引用。

     

    经过上面的详解 ,我们知道了引用其实就是取地址。那么我们都知道一个立即数是没有地址的,即

    int &b = 10;

    这样的代码是无法通过编译的。那如果你就是非要引用一个立即数,其实也不是没有办法:

    const int &b  = 10;

    即将这个立即数用const修饰一下,就可以了。为什么呢?

    这时因为被const修饰的都会产生一个临时量来保存这个数据,自然就有地址可取了。

     

    总结:引用底层就是指针,使用时会直接解引用,可以配合const对一个立即数进行引用。

     

    • 6.malloc,free && new,delete

    这个问题很有意思,也是重点需要关注的问题。malloc()和free()是C语言中动态申请内存和释放内存的标准库中的函数。而new和delete是C++运算符、关键字。new和delete底层其实还是调用了malloc和free。它们之间的区别有以下几个方面:

    ①:malloc和free是函数,new和delete是运算符。

     

    ②:malloc在分配内存前需要大小,new不需要。

    例如:int *p1 = (int *)malloc(sizeof(int));

               int *p2 = new int;     //int *p3 = new int(10);

    malloc时需要指定大小,还需要类型转换。new时不需要指定大小因为它可以从给出的类型判断,并且还可以同时赋初始值。

     

    ③:malloc不安全,需要手动类型转换,new不需要类型转换。

    详见上一条。

     

    ④:free只释放空间,delete先调用析构函数再释放空间(如果需要)。

    与第⑤条对应,如果使用了复杂类型,先析构再call operator delete回收内存。

     

    ⑤:new是先调用构造函数再申请空间(如果需要)。

    与第④条对应,我们在调用new的时候(例如int *p2 = new int;这句代码 ),底层代码的实现是:首先push 4字节(int类型的大小),随后call   operator new函数分配了内存。由于我们这句代码并未涉及到复杂类型(如类类型),所以也就没有构造函数的调用。如下是operator new的源代码,也是new实现的重要函数:

    void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
            {       // try to allocate size bytes
            void *p;
            while ((p = malloc(size)) == 0)
                    if (_callnewh(size) == 0)
                    {       // report no memory
                            _THROW_NCEE(_XSTD bad_alloc, );
                    }
     
            return (p);
            }

    我们可以看到,首先malloc(size)申请参数字节大小的内存,如果失败(malloc失败返回0)则进入判断:如果_callnewh(size)也失败的话,抛出bad_alloc异常。_callnewh()这个函数是在查看new handler是否可用,如果可用会释放一部分内存再返回到malloc处继续申请,如果new handler不可用就会抛出异常。

     

    ⑥:内存不足(开辟失败)时处理方式不同。

    malloc失败返回0,new失败抛出bad_alloc异常。

     

    ⑦:new和malloc开辟内存的位置不同。

    malloc开辟在堆区,new开辟在自由存储区域。

     

    ⑧:new可以调用malloc(),但malloc不能调用new。

    new就是用malloc()实现的,new是C++独有malloc当然无法调用。

    • 7.作用域

    C语言中作用域只有两个:局部,全局。C++中则是有:局部作用域,类作用域,名字空间作用域三种。

    所谓名字空间就是namespace,我们定义一个名字空间就是定义一个新作用域。访问时需要以如下方式访问(以std为例)

    std::cin<< "123" <<std::endl;

    例如我们有一个名字空间叫Myname,其中有一个变量叫做data。如果我们希望在其他地方使用data的话,需要在文件头声明:using Myname::data;这样一来data就使用的是Myname中的值了。可是这样每个符号我们都得声明岂不是累死?

    我们只要using namespace Myname;就可以将其中所有符号导入了。

    这也就是我们经常看到的using namespace std;的意思啦。

    展开全文
  • C语言的头文件宏定义详解

    万次阅读 多人点赞 2018-01-24 21:00:44
    之前对C语言的头文件宏定义抱着一知半解的态度,现理清思路并以文字的形式整理出来,以供不时之需 头文件 头文件,顾名思义就是定义在C语言文件头部的那一坨东西 #include 这就是一个标准输入输出的...

    之前对C语言的头文件和宏定义抱着一知半解的态度,现理清思路并以文字的形式整理出来,以供不时之需

    头文件

    头文件,顾名思义就是定义在C语言文件头部的那一坨东西

    #include <stdio.h>
    

    这就是一个标准输入输出的头文件声明,头文件声明写在定义文件(后缀名.c的文件)的头部,并在定义文件中具体实现

    #include <stdlib.h>
    #include "mylib.h"
    

    这是两种声明头文件的方法,其中尖括号表示 “到环境指定的目录去引用”,而双引号表示 “首先在当前目录查找,然后在到环境指定的目录去引用”。在C标准库中每个库函数都在一个头文件中声明,可以通过第一种方式引用。

    头文件的格式

    #ifndef _MYLIB_H_
    #define _MYLIB_H_
    ...
    #endif
    
    

    第一句“ifndef”意思是“如果在导入头文件的文件中之前没有导入该头文件就编译下面的代码”,该句的作用是防止重复导入
    第二句“define”是“宏定义”的意思,表示以下代码是的头文件主体部分
    最后来一句“endif”和“ifdef”首尾呼应
    其中“ifndef”和“define”后面跟的是相同的“标识”,通常和头文件名相同,所有字母均大写并把点号改为下划线即可

    #include "mylib.h"
    

    看到这句话后编译器会把该头文件“mylib.h”复制粘贴到导入的文件中,之后你就可以使用头文件中定义的常量和结构定义了

    显然恰当地使用头文件有利于更好的组织文件和项目

    提请注意
    1、头文件只是声明,不占内存;在编译时会被合并到源文件
    2、头文件和其它C语言文件一样可以引用其它文件,可以写预处理块但是不要出现具体语句
    3、可以在头文件中定义宏函数,其本质上还是一个声明
    4、各个头文件相互独立,标准头文件之间不存在相互包含关系
    5、头文件可以重复引用,但是相当于只导入一次
    6、 从C语法角度讲完全可以在头文件中写任何东西,因为#include在作用上和Ctrl-C + Ctrl-V等效——但是这样时不推荐的;头文件的作用就是更好的组织代码

    何时使用
    1、结构体的定义
    2、函数声明,变量声明,宏定义,常数定义等
    3、当源代码不便公布时使用头文件提供接口
    4、在大项目中需要多文件联合编译

    小栗子

    #ifndef _NODE_H_
    #define _NODE_H_
    
    typedef struct _node{
    	int value;
    	struct _node* next;
    } Node;
    
    #endif
    
    #include "node.h"
    int main(int argc, char const argv[])
    {
    	Node* p = (Node*)malloc(sizeof(Node));
    	...
    	return 0;
    }
    

    常用头文件

    stdio.h 标准输入输出
    stdlib.h 标准常用库
    string.h 字符串函数库
    math.h 数学库
    ctype.h 字符函数库
    time.h 时间库
    windows.h 微软视窗库


    宏定义

    宏定义是C语言提供的三种预处理功能的其中一种,这三种预处理包括:宏定义、文件包含、条件编译。宏定义和操作符的区别是:宏定义是替换,不做计算,也不做表达式求解。“宏定义” 也称 “宏替换”、“宏”。

    define PI 3.1415926
    

    这就是一个简单的宏,在程序的预处理阶段宏名会被替换为后面的字符串

    传入参数的宏

    1、 #:字符串化操作,即将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串,使用时置于宏定义体中的参数名前,如:

    #define func(para)  #para
    ...
    char str[] = func(hello); //被展开为:char str[]="hello"
    

    说明:如果传入的参数之前有空格则忽略之,如果参数之间有多个空格则在连成字符串时只算一个

    2、 #@:字符化操作,即将宏定义传入的参数名转换为用一对单引号扩起来的参数名字符串,使用时置于参数名前,如:

    #define fun(pa) #@pa
    char a = fun(a); //被展开为char a='a';
    

    3、 ##:参数连接操作,即将宏定义的多个形参连接成一个实际参数,如:

    #define COMMAND(a, b) a##b
    ...
    COMMAND(1, 2); //相当于12
    CMOOAND(ac, b); //相当于acb
    

    4、\:当前行继续操作,实际上是对换行符转义,如:

    #define LOOP(FROM, TO, CONTENT)\
    for (int i = FROM; i < TO; i++) {\
        CONTENT\
    }
    

    5、 __VA_ARGS__:变长参数操作,即可以接受不定个数的参数,如:

    #define eprintf(...) fprintf (stderr, __VA_ARGS__)
    eprintf ("%s:%d: ", input_file, lineno)
    //==>  fprintf (stderr, "%s:%d: ", input_file, lineno)
    

    为什么要使用宏定义

    简而言之,使用宏定义可以提高代码的可读性。具体的说,可以减少magic number的使用,并且可以做一些方便的替换,如下面的代码:

    #define MALLOC(n, type) (type*)malloc((n) * sizeof(type))
    

    使用时,int *p=MALLOC(10, int); 即可

    宏的规范写法
    1、宏名大写
    2、宏定义语句末尾不加分号
    3、宏函数的形式参数不加类型

    另外宏定义需要注意的
    1、宏定义可以嵌套
    2、宏定义不能出现在字符串的“”中
    3、宏定义不分配内存,变量定义才分配内存
    4、宏定义只是简单的替换,而且是直接对源码的字符串替换,如下面的宏定义就不能很好的表达求平方函数:

    #define sqrt(x) x * x
    ...
    int y = sqrt(1 + 2); //y = 1 + 2 * 1 + 2 = 5 ≠9
    

    这时候加上括号就好了:

    #define sqrt(x) (x) * (x)
    ...
    int y=sqrt(1+2); //y = (1 + 2) * (1 + 2) = 9
    

    5、宏函数和自定义函数相比,效率更高但是安全性低且会使编译生成的目标文件变大;宏函数没有分配和释放栈帧、传参、传返回值等一系列工作,适合那些简短并且频繁调用的函数,但是对于递归则不推荐使用宏



    (补;GCC 的一个小技巧)

    #include <stdio.h>
    
    #define GREATER(a, b) ({\
    	int ret;\
    	if (a > b) ret = a;\
    	else ret = b;\
    	ret;\
    })
    
    
    int main(void)
    {
    	printf("1 and 2, which is greater? %d\n", GREATER(1, 2)); //2
    	printf("6 and 5, which is greater? %d\n", GREATER(6, 5)); //6
    }
    

    可以把define当简陋的的函数返回来用。注意到这是一个gcc扩展,MSVC不能用哦。

    #define WHATEVER() ({\
    	sd::cout << "WHATEVER" << std::endl;
    }) // 报错,因为没有返回值
    
    #define WHATEVER() ({\
    	std::cout << "WHATEVER" <<std::endl;\
    	int datafield = 1024;\ // 用数据冒充返回值,这样就不会报错辽
    }
    

    如果是一般的void类型的函数,也就是不带返回值的函数(in other words, 子例程、代码块……),我们更常用的是用一个do-while(0) 括起来,非常简单,可惜就是不能带返回值。

    #include <iostream>
    
    #define WHATEVER() do \{
    	std::cout << "whatever" << std::endl;\
    } while (0)
    
    
    int main(void)
    {
    	WHATEVER(); // "whatever"
    }
    

    (完)

    展开全文
  • 聊聊C语言和指针本质

    万次阅读 多人点赞 2019-11-24 21:07:34
    很多编程语言都以 “没有指针” 作为自己优势来宣传,然而,对于C语言,指针却是与生俱来。 那么,什么是指针,为什么大家都想避开指针。 很简单, 指针就是地址,当一个地址作为一个变量存在时,它就被叫做指针...

    坐着绿皮车上海到杭州,24块钱,很宽敞,在火车上非正式地聊几句。


    很多编程语言都以 “没有指针” 作为自己的优势来宣传,然而,对于C语言,指针却是与生俱来的。

    那么,什么是指针,为什么大家都想避开指针。

    很简单, 指针就是地址,当一个地址作为一个变量存在时,它就被叫做指针,该变量的类型,自然就是指针类型。

    指针的作用就是,给出一个指针,取出该指针指向地址处的值。为了理解本质,我们从计算机模型说起。

    宏观看来,计算机可以分为两类:

    1. 存储-执行计算机。
      这类机器典型的例子就是我们平时使用的计算机,有一个CPU,有一个内存,CPU仅包含运算逻辑,所有的指令和数据都在内存中,内存仅供存储,不包含任何运算组件。
    2. 现场编程计算机。
      这类机器的典型例子就是ASCI电路,FPGA这种。直接针对特定的需求构建逻辑电路,然而,由于存在笛卡尔积的问题,不太适合通用计算。

    我们看我们平时使用的存储-执行模型的计算机工作模式:

    • CPU在地址总线上发射一个地址到内存。
    • 内存把特定地址对应的数据返回到数据总线。

    看起来,通用计算机就是通过指针完成所有工作的。CPU没有能力直接操作内存里的值,它必须做以下的操作以迂回:

    1. 从特定地址A0取出值V0。
    2. 对V0进行加工运算生成V1。
    3. 将V1存入特定地址A1。

    太初,人们就是按照以上的这么个逻辑编程的,这就是汇编语言:

    mov    -0x4c(%rbp),%ebx
    

    然而,这样太麻烦了,C语言随着简单通用的UNIX操作系统而生,下面的语句看起来更加方便:

    int a = 10;
    char *p = &a;
    *p = 13;
    

    C语言直接映射了CPU的工作方式,而且是用极其简单的方式,这就是C语言的艺术。

    这就是C指针的背景。在那个年代,人们还没有渴望计算机帮助完成更复杂的业务逻辑,人们只是希望用一种更加简单的方式抽象出计算机的行为,最终的结晶,就是C语言。

    于是,我们说,C语言的精华就是指针,指针是C语言的一切。我们可以没有if-else语言,我们可以没有switch-case语句,我们可以不要while,我们不要for,但我们必须有指针。

    是的,我们可以用指针函数的状态矩阵代替if-else之类:

    int (*routine)[...]();
    ...
    condition = calc(...);
    routine[condition](argv);
    

    我们用状态矩阵成功规避了if-else…可以看到,还是用的指针。


    指针是存储-执行模型的计算机工作的必要条件!

    我们再看存储-执行模型的计算机的工作方式:

    • 给定一个地址,CPU就可以取出该地址的数据。
    • 给定一个地址,CPU就可以写入该地址一个值。

    这意味着什么?

    只要想让CPU正常工作,就必须暴露整个内存地址空间给CPU,否则CPU就是一堆毫无用处的门电路,换句话说, 一切来自内存! 操作内存就必然要用指针!


    其实,C语言就是简化版的汇编语言。最终,C语言接力汇编用指针创造了世界。

    不管怎么样,C语言是面向计算机的编程语言,而不是面向业务的编程语言,它映射了计算机的工作方式而不太善于描述业务逻辑,因此,C语言深受黑客,编程手艺人这种计算机本身的爱好者喜爱,却不被业务程序员待见,因为摆弄指针确实太繁琐复杂了,一不小心就会出错。

    存储-执行模型的问题在于,要设计复杂的带外机制防止内存被任意访问,由此而来的就是复杂的分段,分页,访问控制,MMU等机制,当然,这些机制和CPU依靠指针访问内存的工作方式并不冲突。

    把C语言指针用的最绝的应该就是Linux内核的嵌入式链表 struct list_head 了:

    struct list_head {
    	struct list_head *next, *prev;
    };
    

    它可以代表一切,它通过C指针完美诠释了OOD,list_head是世界的基类!

    通过container_of宏,list_head可以转换为任意对象:

    /**
     * container_of - cast a member of a structure out to the containing structure
     * @ptr:        the pointer to the member.
     * @type:       the type of the container struct this is embedded in.
     * @member:     the name of the member within the struct.
     *
     */
    #define container_of(ptr, type, member) ({                              \
            void *__mptr = (void *)(ptr);                                   \
            BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&   \
                             !__same_type(*(ptr), void),                    \
                             "pointer type mismatch in container_of()");    \
            ((type *)(__mptr - offsetof(type, member))); })
    

    这个转换背后的依赖,正是指针:
    在这里插入图片描述

    然而,C语言依然对业务编程不友好,前面说了,C语言映射的就是计算机工作方式本身,若想用好C语言,就必须要懂计算机原理,这并不是业务程序员的菜,业务程序员只是编写业务逻辑,并不在乎计算机是如何工作的。

    曾经,计算机还是一群痴迷于技术本身的极客们的玩具,计算机是属于他们的,他们用C编程,用Perl/Python/Bash粘合二进制程序。进入互联网时代,随着越来越复杂的业务逻辑出现,越来越多的职业程序员开始成了多数派,他们开始使用更加业务友好的语言,Java,Go便成功了。

    不能说这些业务编程语言没有指针,只是它们隐藏了指针而已,它们对程序员暴露了更加对业务友好的编程接口和语法,自己在底层处理指针问题,仅此而已。指针是客观存在的,只要你使用的是存储-执行模型的计算机,指针就是一切。


    浙江温州皮鞋湿,下雨进水不会胖。

    展开全文
  • 100个C语言经典小程序和C语言编写小游戏

    万次阅读 多人点赞 2019-01-29 22:17:17
    100个C语言经典小程序和C语言编写小游戏
    展开全文
  • C语言的优点缺点

    万次阅读 多人点赞 2016-01-26 14:07:11
    C语言的优点缺点
  • c语言的传值传址

    千次阅读 2018-04-13 11:09:28
    C语言的传值传址
  • C语言函数定义声明

    万次阅读 多人点赞 2019-08-08 14:38:17
    本科学C语言的时候,就对函数的定义声明的作用很迷糊,刚看到一篇博客,写得非常清楚,贴出来与各位共享! 一、函数的声明 1.在C语言中,函数的定义顺序是有讲究的:默认情况下,只有后面定义的函数才可以调用...
  • c语言和java区别

    千次阅读 2015-10-10 10:11:57
    今晚读了一下c程序设计语言,这是一本经典书籍,发现C语言和java有很多是相同,毕竟java是由c语言进化来。我大概从我自己思考来谈谈不同点1.c语言是面向过程,主要单位是函数,变量函数耦合性比较差,大...
  • 关于C语言编译器和C语言标准

    千次阅读 2017-11-02 09:09:57
    很多同学在刚开始学习C语言的时候,基本不会也不用去了解C语言的基本信息,比如,C语言标准C11C99。还有的同学不知道C语言编译器究竟是个什么东西,有何作用。为了解答初学者的疑惑,笔者给大家简单明了的解释一下...
  • c语言入门自学 零基础学c语言教程

    万次阅读 多人点赞 2019-05-04 14:07:32
    C语言通过自学软件工程师要多久? 许多同学问C语言怎么学?... 我是一个教师,已经开发了很多年程序,很多刚刚起步人一样,学习第一个计算机语言就是C语言。经过这些年开发,我深深体会到C语言对于一...
  • 自己编写数字信号处理中卷积互相关运算的C语言代码,调试可用
  • C语言的makeMakefile

    万次阅读 2017-02-16 23:02:03
    C语言的makeMakefilemakeMakefile的介绍makeMakefile1:makeMakefile的介绍:1:概念 在软件的工程中的源文件是不计其数的,其按照类型,功能,模块分别放在若干个目录中,哪些文件需要编译,那些文件需要后...
  • C语言getcharputchar函数用法

    千次阅读 2020-02-18 17:00:02
    C语言getcharputchar函数的用法: 一、字符输入函数getchar: getchar函数是从标准的输入设备(如键盘)上输入一个字符,不带任何参数,格式为: getchar(): ... //getchar是C语言的标准库函数...
  • c语言longlong long取值范围

    万次阅读 多人点赞 2018-10-20 15:06:15
    C语言的整型溢出问题 整数溢出 int、long int 、long long int 占用字节 《C指针》中写过:long与int:标准只规定long不小于int的长度,int不小于short的长度。 double与int类型的存储机制不同,long int的8个...
  • C语言的关键字标识符

    千次阅读 多人点赞 2017-07-09 15:00:39
    一、关键字    关键字  C语言的关键字都是用小写字母书写的,不能用大写字母书写。
  • c语言和c++相互调用

    千次阅读 2017-06-16 15:47:58
    在实际项目开发中,cc++代码的相互调用是常见的,c++能够兼容c语言的编译方式,但是c++编译器g++默认会以c++的方式编译程序,而c程序编译器gcc会默认以c的方式编译它,所以cc++的相互调用存在一定的技巧。...
  • c语言和python区别

    千次阅读 2019-12-04 11:19:41
    c语言和python区别 1、语言类型不同。 Python是一种动态类型语言,又是强类型语言。它们确定一个变量类型是在您第一次给它赋值时候。C 是静态类型语言,一种在编译期间就确定数据类型语言。大多数静态类型...
  • C语言复制函数(strcpymemcpy)

    万次阅读 2016-05-14 17:15:27
    PS:初学算法,开始刷leetcode,Rotate array预备知识(写...这个不陌生,大一学C语言讲过,其一般形式为strcpy(字符数组1,字符串2)作用是将字符串2复制到字符数组1中去。 EX: strncpy(str1,str2,2);作用是将st
  • C语言的语法结构总结

    千次阅读 2018-05-14 23:19:23
    从大一开始学习C语言,到现在已经有很长时间了,C语言的中级学习应该算是结束了。本文是对C的总结,作为第一种认真学习的编程语言,希望在以后的学习中,能够越来越深入学习。相信在有C的基础之后,学习其他语言应该...
  • C语言和C++区别与联系(详细)

    万次阅读 多人点赞 2018-08-19 10:47:32
    面向过程面向对象语言区别 ...我们都知道C语言是面向过程语言,而C++是面向对象语言,说CC++区别,也就是在比较面向过程面向对象区别。 1.面向过程面向对象区别 (1)面向...
  • C语言和Java优缺点

    千次阅读 2019-04-17 20:31:36
    Java、C++、Python、C#、JavaScript 等高级编程语言相比,C语言涉及到编程概念少,附带标准库小,所以整体比较简洁,容易学习,非常适合初学者入门,但是深入话就会比较艰难。 优点: 1 语言简洁,使用方便...
  • Linux:C语言的标准编译器

    千次阅读 2016-02-02 09:15:17
    本文简要介绍C语言标准历史和C语言编译器。
  • C++和C语言的区别

    万次阅读 2016-09-18 08:17:24
    C++和C语言的区别,绝对不是仅仅只是把结构体里面加一个函数指针。 C++的最高明或者说高级语言的最高明之处是它的多态设计,实现了代码的 抽象化,然后实际使用的时候,语言就会聪明的做我们想做的事。
  • C语言形参实参区别(非常详细)

    千次阅读 多人点赞 2019-11-03 17:45:23
    C语言函数参数会出现在两个地方,分别是函数定义处函数调用处,这两个地方参数是有区别。 形参(形式参数) 在函数定义中出现参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来...
  • C语言函数主函数调用

    千次阅读 2017-10-20 22:53:33
    C语言中主函数在调用子函数时,会产生相应栈去存放调用子函数时值,当调用完毕时,调用子函数所生成栈则立即销毁。主函数在调用子函数时如果为值传递时,则传过去参数并不影响主函数中参数值,如果采用...
  • c语言和汇编语言区别

    万次阅读 多人点赞 2016-11-04 17:57:16
    C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言;尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准...
  • C语言指针地址区别

    千次阅读 2017-02-23 20:59:16
    C语言指针地址区别
  • C语言和JAVA区别在哪里?

    千次阅读 2017-08-05 11:14:34
    Java是面向对象的语言,执行效率比C语言低。 C语言最关键的是比Java多了指针,这也说明了Java的健壮性,还有Java的...C语言的安全性不如Java,C语言没有Java的垃圾回收机制,申请的空间要手动释放。 Java的通用性好,
  • go语言和c语言的区别

    千次阅读 2019-04-22 17:26:04
    我们基本上都学过c语言 那么为什么和c语言相似go语言这几年火起来了那 我们可以看看cgo之间区别 看完你就会发现go语言为什么这么火 他是有多么简介~~~ 区别:http://hyperpolyglot.org/c ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 189,413
精华内容 75,765
关键字:

c语言的和

c语言 订阅