如何自学c语言 (如何自学C语言编程)
本文目录导航:
如何自学c语言
本次回答邀请boolean,由他来分享自己在学习C++过程中踩过的那些坑~相信对自学C++的朋友会有帮助~

C++跟《九阴真经》一样,如果使用不当,很容易落得跟周芷若、欧阳锋、梅超风等一样走火入魔。
这篇文章总结了在学习C++过程中容易走火入魔的一些知识点。
为了避免篇幅浪费,太常见的误区(如指针和数组、重载、覆盖、隐藏等)在本文没有列出,文中的知识点也没有前后依赖关系,各个知识点基本是互相独立,并没有做什么铺垫,开门见山直接进入正文。
1 函数声明和对象定义
2 静态对象初始化顺序
3 类型转换
3.1 隐式转换
3.2 显示转换
4 inline内联
5 名称查找
5.1 受限名称查找
5.2 非受限名称查找
6 智能指针
6.1 std::auto_ptr
6.2 std::shared_ptr
6.3 std::unique_ptr
7 lambda表达式
1 函数声明和对象定义
对象定义写成空的初始化列表时,会被解析成一个函数声明。
可以采用代码中的几种方法定义一个对象。
中使用变量y来初始化x
如果初始化顺序不一样,两次执行的结果输出不一样,如下所示:
如果我们需要指定依赖关系,比如y依赖x进行初始化,可以利用这样一个特性来实现:函数内部的静态对象在函数第一次调用时初始化,且只被初始化一次。
使用该方法,访问静态对象的唯一途径就是调用该函数。
改写后代码如下所示:
y对象使用x对象进行初始化
3 类型转换
这里只描述自定义类的类型转换,不涉及如算数运算的类型自动提升等。
3.1 隐式转换
C++自定义类型在以下两种情况会发生隐式转换:
1) 类构造函数只有一个参数或除第一个参数外其他参数有默认值;2) 类实现了operator type()函数;
隐式类型转换在某些场景中确实比较方便,如:
a、运算符重载中的转换,如可以方便的使Integer类型和内置int类型进行运算
隐式类型转换在带来便利性的同时也带来了一些坑,如下所示:
operator type()带来的坑。
上述String类存在到const char *的隐式转换,strcat函数返回时String隐身转换成const char *,而String对象已经被销毁,返回的const char *指向无效的内存区域。
这也是std::string不提提供const char *隐式转换而专门提供了c_str()函数显示转换的原因。
3.2 显示转换
正是由于隐式转换存在的坑,C++提供explicit关键字来阻止隐式转换,只能进行显示转换,分别作用域构造函数和operator(),如下所示:
1) explicit Ctor(const type );2) explicit operator type();
为了保持易用性,C++11中explicit operator type()在条件运算中,可以进行隐式转换,这就是为什么C++中的智能指针如shared_ptr的operator bool()加了explicit还能直接进行条件判断的原因。
下面代码来自shared_ptr源码。
2)通过函数指针对inline函数进行调用时,编译器有可能不实施inline
4)inline函数有局部静态变量时,可能无法内联
二分查找的递归方式实现。
分别调用二分查找的递归和迭代实现,开启-O1优化,通过查看汇编代码和nm查看可执行文件可执行文件符号,只看到了递归版本的call指令和函数名符号,说明递归版本没有内联,而迭代版本实施了内联展开。
表面上构造函数定义为空且是inline,但编译器实际会生成如右侧的伪代码来构造基类成分和成员变量,从而不一定能实施inline。
5 名称查找
C++中名称主要分为以下几类:
a) 受限型名称:使用作用域运算符(::)或成员访问运算符(.和-)修饰的名称。
如:::std、std::sort、、this-foo等。
b) 非受限型名称:除了受限型名称之外的名称。如:name、foo
c) 依赖型名称:依赖于形参的名称。如:vectorT::iterator
d) 非依赖型名称:不属于依赖型名称的名称。如:vectorint::iterator
5.1 受限名称查找
受限名称查找是在一个受限作用域进行的,查找作用域由限定的构造对象决定,如果查找作用域是类,则查找范围可以到达基类。
5.2.2 ADL(argument-dependent lookup)查找:又称koenig查找,由C++标准委员会Andrew Koenig定义了该规则——如果名称后面的括号里提供了一个或多个类类型的实参,那么在名称查找时,ADL将会查找实参关联的类和命名空间。
上面的代码输出什么? 1 1 5 10吗。上面的代码无法编译通过,提示如下错误
把operator定义移到ns命名空间后运行结果正常
这段代码编译时提示如下错误,我们用int *实例化D1的模板参数并给m_value赋值,编译器提示无法将int *转换成int类型,也就是m_value被实例化成了int而不是int *。
6.2 std::shared_ptr
std::shared_ptr采用引用计数共享指针指向对象所有权的智能指针,支持复制语义。
每次发生复制行为时会递增引用计数,当引用计数递减至0时其管理的对象资源会被释放。
但shared_ptr也存在以下几个应用方面的陷阱。
1)勿通过传递裸指针构造share_ptr
这段代码使用同一个this指针构造了两个没有关系的shared_ptr,在离开作用域时导致重复析构问题,和1)是一个道理。
当希望安全的将this指针托管到shared_ptr时,目标对象类需要继承std::enable_shared_from_thisT模板类并使用其成员函数shared_from_this()来获得this指针的shared_ptr对象。
如下所示:
这段代码可能发生内存泄漏。一般情况下,这段代码的调用顺序如下:
new Handler() ① 在堆上创建Handler对象
shared_ptr()②创建shared_ptr
getData() ③调用getData()函数
但是编译器可能不按照上述①②③的顺序来生成调用代码。
可能产生①③②的顺序,此时如果③getData()产生异常,而new Handler对象指针还没有托管到shared_ptr中,于是内存泄漏发生。
使用make_shared可以避免这个问题。
6.3 std::unique_ptr
std::unique_ptr是独占型智能指针,仅支持移动语义,不支持复制。
默认情况下,unique_ptr有着几乎和裸指针一样的内存开销和指令开销,可以替代使用裸指针低开销的场景。
1)与shared_ptr不同,unique_ptr可以直接指向一个数组,因为unique_ptr对T[]类型进行了特化。
如果shared_ptr指向一个数组,需要显示指定删除器。
7 lambda表达式
1)捕获了变量的lambda表达式无法转换为函数指针。
3)默认情况下,lambda无法修改按值捕获的变量。
如果需要修改,需要使用mutable显示修饰。
这其实也好理解,lambda会被编译器转换成operator() const的函数对象。
C++面试八股文:指针占用多少个字节?
在C++的面试中,关于指针占用多少字节的问题,通常会根据操作系统的位数来回答。
具体而言,32位系统下指针占用4个字节,而64位系统下则占用8个字节。
这是因为,指针的大小取决于它能表示的最大内存地址的长度。
在32位系统中,内存地址使用32位二进制表示,能表示的最大值为4GB,所以指针占用4个字节。
而在64位系统中,内存地址使用64位二进制表示,能表示的最大值为16EB(约为18.4亿TB),因此指针需要占用8个字节来表示。
需要注意的是,指针的大小与操作系统的位宽相关,位宽决定了系统能寻址的内存大小。
例如,32位操作系统的位宽为32位,其寻址范围为4GB,而64位操作系统的位宽理论上可达64位,但实际使用中仅需48位,以寻址更广阔的内存。
在C++中,指针是基础概念之一,它在程序设计中扮演着重要角色。
然而,随着现代C++的发展,RAII、智能指针、容器等概念的普及,裸指针在项目中的使用逐渐减少。
尽管如此,指针仍然是C和C++语言的重要组成部分,掌握其特性对于开发者至关重要。
若指针的长度与操作系统的位宽不匹配,可能会导致程序错误。
因此,在编写代码时,需注意指针的大小与操作系统的位宽相匹配,以确保程序的正确性和稳定性。
对于一个只懂C语言得人来说,怎么理解句柄?
句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。
WINDOWS句柄有点象C语言中的文件句柄。
WINDOWS句柄有点象C语言中的文件句柄。
,句柄是一种特殊的智能指针。
当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
2,整个Windows编程的基础。