C++学习笔记为啥不从C++基础学习开始呢?
选择从实际的C++功能开发开始,这和个人的工作经历有关。由于长期从事系统设计和软件调试相关工作,对于各种软件系统、开发语言的熟练使用,成了个人必备的技能。当功能实现能力积累到一定程度后,你会逐渐把注意力转向效率和语言特性上来。这时候,代码规范、设计模式、高级语言特性、低级语言能力、复杂算法、数据结构慢慢的成为工作中无法回避的内容。打牢基础是构建技能高峰的充要条件,现在开始C++基础学习还不晚!
C++基础学习相关的书籍文章很多,我们可以通过下面两个网站完成相关学习:
https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5
C++基础学习重点在于——积累!
对C++参考手册中的基础数据类型、数据结构、算法、函数的熟练掌握是C++基础的重点。反复的研读手册是学习任何语言、SDK的唯一有效方法,当然我们不能忽略强大的互联网!好记心敌不了烂笔头,下面列举一些个人在C++基础学习中比较容易犯错的知识点与大家共勉。
C++基础学习难点汇总
为了与之前的常用功能学习的代码分开,我们在NoteBook中新建一个BasicPanel来进行C++基础学习代码的记录。
在codeblocks中打开SDKLearn项目,在主菜单的wxSmith菜单中新建Panel:
在资源编辑窗口中拖拽BasicPanel资源到合适大小,方便后续添加控件。(SystemPanel中也需要同样调整ListCtrl控件大小用以在文件监控时完整显示文件列表)
在主窗口源文件SDKLearnMain.cpp中引入Panel头文件并创建Panel指针变量
//引入Panel头文件
#include "BasicPanel.h"
//创建Panel指针变量
BasicPanel* m_BasicPanel;
在主窗口源文件SDKLearnMain.cpp中的主窗口初始化函数SDKLearnFrame::SDKLearnFrame(wxWindow* parent,wxWindowID id)中添加如下代码,实现向m_SDKLearnNotebook中添加m_SystemPanel页的效果。
//在初始化函数中添加Panel到NoteBook中
m_BasicPanel=new BasicPanel(m_SDKLearnNotebook);
m_SDKLearnNotebook->AddPage(m_BasicPanel,L"BasicPanel");
编译并运行程序,程序效果如下:
由于希望在不同的Panel中学习时,通过各自不同的菜单来触发不同的功能。我们先在不同Panel中创建不同的功能菜单,然后在不同的Panel中通过右键来展现Panel对应的菜单来触发不同功能 。下面来介绍如何实现:
进入Panel资源编辑窗口,在Tools控件中选择创建wxMenu控件
重命名菜单控件如下:
在Panel资源编辑窗口中双击菜单控件,进入菜单编辑窗口
添加菜单项,作为新学习功能的触发菜单项
修改菜单项变量名为可读性强的名称,添加菜单响应函数
在响应函数中添加需要实现的功能的代码。
在Panel资源编辑窗口的Panel属性编辑区为Panel添加右键弹起事件处理函数
在Panel右键弹起事件中增加如下代码,实现专属菜单的呼出:
void BasicPanel::OnRightUp(wxMouseEvent& event)
{
PopupMenu( &m_BasicMenu, event.GetX(), event.GetY() );
}
至此,我们完成了该主题的某功能的实现,并在主程序中通过主题对应的Panel窗口右键菜单来发起该功能的调用。
编译并运行程序即可验证该功能是否达到预期。
这里我们也可以通过SystemPanel中的控制台窗口来显示标准输出的内容
在主窗口菜单中显示Panel页的右键菜单
通过右键展现Panel菜单的方法简单明了,但是对于Panel中界面元素较复杂时采用右键呼出菜单的交互方式可能无法适用,此时我们希望实现通过主菜单来呼出Panel中的右键菜单的功能,下面我们来讲解如何实现。
首先, 在主菜单中增加一个菜单入口
在系统消息函数MSWWindowProc中添加如下代码,截获菜单消息:
//处理系统消息和自定义消息
WXLRESULT SDKLearnFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{
switch (message)
{
case WM_SYSCOMMAND:
{
switch(wParam)
{
case 0xF095://选中菜单事件
{
int xPos = GET_X_LPARAM(lParam); // horizontal position
int yPos = GET_Y_LPARAM(lParam); // vertical position
this->ScreenToClient(&xPos,&yPos);
if(m_StudyMenu->GetTitle ().Contains("System"))
{
cout<<"选中了System菜单栏"<<endl;
m_SystemPanel->PopupMenu( &m_SystemPanel->m_SystemMenu, xPos, yPos );
}
else if(m_StudyMenu->GetTitle ().Contains("Basic"))
{
cout<<"选中Basic菜单栏"<<endl;
m_BasicPanel->PopupMenu( &m_BasicPanel->m_BasicMenu, xPos, yPos );
}
else
{
cout<<"选中其他菜单栏"<<endl;
}
}
break;
default:
break;
}
}
break;
default:
break;
}
return wxFrame::MSWWindowProc(message, wParam, lParam);
}
在代码中,由于系统菜单消息的wParam参数不在参考手册中,我们可以在调试模式下在Watches窗口中得到wParam参数值,转换为16进制就是0xF095,通过在系统消息处理函数中对该数值的监控即可实现在点击系统菜单栏时的相关菜单变化处理。这里需要注意的是必须通过Panel的指针变量来显示相应的菜单,不然菜单在Panel中的响应函数无法被调用。
基础数据类型及长度
这个知识点在跨系统编程时很容易因为忽略相同类型在不同操作系统和编译环境下的长度差异而出现无法预估的问题。下面截取菜鸟的C++类型及长度表与大家共勉:
下表显示了各种变量类型在内存中存储值时需要占用的内存,以及该类型的变量所能存储的最大值和最小值。
注意:不同系统会有所差异,一字节为 8 位。
注意:默认情况下,int、short、long都是带符号的,即 signed。
注意:long int 8 个字节,int 都是 4 个字节,早期的 C 编译器定义了 long int 占用 4 个字节,int 占用 2 个字节,新版的 C/C++ 标准兼容了早期的这一设定。
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 |
signed char | 1 个字节 | -128 到 127 |
int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 |
signed int | 4 个字节 | -2147483648 到 2147483647 |
short int | 2 个字节 | -32768 到 32767 |
unsigned short int | 2 个字节 | 0 到 65,535 |
signed short int | 2 个字节 | -32768 到 32767 |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int | 8 个字节 | 0 到 18,446,744,073,709,551,615 |
float | 4 个字节 | 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
double | 8 个字节 | 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
long double | 16 个字节 | 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。 |
wchar_t | 2 或 4 个字节 | 1 个宽字符 |
注意,各种类型的存储大小与系统位数有关,但目前通用的以64位系统为主。
以下列出了32位系统与64位系统的存储大小的差别(windows 相同):
通过下面的代码我们可以清楚不同系统下相同数据类型的长度:
#include<iostream>
#include <limits>
using namespace std;
void BasicPanel::Onm_DataTypeSelected(wxCommandEvent& event)
{
cout << "type: \t\t" << "************size**************"<< endl;
cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);
cout << "\t最大值:" << (numeric_limits<bool>::max)();
cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;
cout << "char: \t\t" << "所占字节数:" << sizeof(char);
cout << "\t最大值:" << (numeric_limits<char>::max)();
cout << "\t\t最小值:" << (numeric_limits<char>::min)() << endl;
cout << "signed char: \t" << "所占字节数:" << sizeof(signed char);
cout << "\t最大值:" << (numeric_limits<signed char>::max)();
cout << "\t\t最小值:" << (numeric_limits<signed char>::min)() << endl;
cout << "unsigned char: \t" << "所占字节数:" << sizeof(unsigned char);
cout << "\t最大值:" << (numeric_limits<unsigned char>::max)();
cout << "\t\t最小值:" << (numeric_limits<unsigned char>::min)() << endl;
cout << "wchar_t: \t" << "所占字节数:" << sizeof(wchar_t);
cout << "\t最大值:" << (numeric_limits<wchar_t>::max)();
cout << "\t\t最小值:" << (numeric_limits<wchar_t>::min)() << endl;
cout << "short: \t\t" << "所占字节数:" << sizeof(short);
cout << "\t最大值:" << (numeric_limits<short>::max)();
cout << "\t\t最小值:" << (numeric_limits<short>::min)() << endl;
cout << "int: \t\t" << "所占字节数:" << sizeof(int);
cout << "\t最大值:" << (numeric_limits<int>::max)();
cout << "\t最小值:" << (numeric_limits<int>::min)() << endl;
cout << "unsigned: \t" << "所占字节数:" << sizeof(unsigned);
cout << "\t最大值:" << (numeric_limits<unsigned>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned>::min)() << endl;
cout << "long: \t\t" << "所占字节数:" << sizeof(long);
cout << "\t最大值:" << (numeric_limits<long>::max)();
cout << "\t最小值:" << (numeric_limits<long>::min)() << endl;
cout << "unsigned long: \t" << "所占字节数:" << sizeof(unsigned long);
cout << "\t最大值:" << (numeric_limits<unsigned long>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned long>::min)() << endl;
cout << "double: \t" << "所占字节数:" << sizeof(double);
cout << "\t最大值:" << (numeric_limits<double>::max)();
cout << "\t最小值:" << (numeric_limits<double>::min)() << endl;
cout << "long double: \t" << "所占字节数:" << sizeof(long double);
cout << "\t最大值:" << (numeric_limits<long double>::max)();
cout << "\t最小值:" << (numeric_limits<long double>::min)() << endl;
cout << "float: \t\t" << "所占字节数:" << sizeof(float);
cout << "\t最大值:" << (numeric_limits<float>::max)();
cout << "\t最小值:" << (numeric_limits<float>::min)() << endl;
cout << "size_t: \t" << "所占字节数:" << sizeof(size_t);
cout << "\t最大值:" << (numeric_limits<size_t>::max)();
cout << "\t最小值:" << (numeric_limits<size_t>::min)() << endl;
cout << "string: \t" << "所占字节数:" << sizeof(string) << endl;
// << "\t最大值:" << (numeric_limits<string>::max)() << "\t最小值:" << (numeric_limits<string>::min)() << endl;
cout << "type: \t\t" << "************size**************"<< endl;
}
这里有个小技巧,如何把正在学习的主题Panel放在主程序默认打开位置?我们仅需要在向NoteBook中添加Panel时指明是否默认选中你希望默认打开的Panel即可,代码如下:
//在初始化函数中添加Panel到NoteBook中
m_SystemPanel=new SystemPanel(m_SDKLearnNotebook);
m_SDKLearnNotebook->AddPage(m_SystemPanel,L"SystemPanel");
//在初始化函数中添加Panel到NoteBook中
m_BasicPanel=new BasicPanel(m_SDKLearnNotebook);
m_SDKLearnNotebook->AddPage(m_BasicPanel,L"BasicPanel",true);
运算符
算术运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
-- | 自减运算符,整数值减少 1 | A-- 将得到 9 |
关系运算符
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
逻辑运算符
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
位运算符
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
运算符 | 描述 | 实例 |
---|---|---|
& | 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 | (A & B) 将得到 12,即为 0000 1100 |
| | 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 | (A | B) 将得到 61,即为 0011 1101 |
^ | 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 | (A ^ B) 将得到 49,即为 0011 0001 |
~ | 二进制补码运算符是一元运算符,具有"翻转"位效果。 | (~A ) 将得到 -61,即为 1100 0011,2 的补码形式,带符号的二进制数。 |
<< | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 | A >> 2 将得到 15,即为 0000 1111 |
赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
其他运算符
运算符 | 描述 |
---|---|
sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
, | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
.(点)和 ->(箭头) | 成员运算符用于引用类、结构和共用体的成员。 |
Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
& | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
* | 指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。 |
运算符优先级
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
指针、引用、值的使用
作为函数参数时
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 |
指针调用 | 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
引用调用 | 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
指针关键知识
概念 | 描述 |
---|---|
C++ Null 指针 | C++ 支持空指针。NULL 指针是一个定义在标准库中的值为零的常量。 |
C++ 指针的算术运算 | 可以对指针进行四种算术运算:++、--、+、- |
C++ 指针 vs 数组 | 指针和数组之间有着密切的关系。 |
C++ 指针数组 | 可以定义用来存储指针的数组。 |
C++ 指向指针的指针 | C++ 允许指向指针的指针。 |
C++ 传递指针给函数 | 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 |
C++ 从函数返回指针 | C++ 允许函数返回指针到局部变量、静态变量和动态内存分配。 |
数组名实际上是一个指向数组开头的常量,所以可以作为右值使用,但不能作为左值修改其值。
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
引用与指针的不同
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
引用通常用于函数参数列表和函数返回值。
概念 | 描述 |
---|---|
把引用作为参数 | C++ 支持把引用作为参数传给函数,这比传一般的参数更安全。 |
把引用作为返回值 | 可以从 C++ 函数中返回引用,就像返回其他数据类型一样。 |