你好这里是limou3434的C++博文系列,对于C++,我计划出一个学习专栏,预计在8月份初步完成,这些都是我学习C++时的一些体会,能被您所任用真的是太好了。
感兴趣的话,您还可以看看我的其他博文系列。
本次我给您带来的内容主要是C++的一些基础知识,如果您学过一门语言(最好是C语言)学起来就会轻松一些。本文主要是指出一些和C语言编程中有比较大不同的地方。
C++比C语言多了很多关键字,具体可以到菜鸟教程粗略看一看了解一下。
在C++中,变量和函数和类都是大量存在的,这些名字都会存储在全局作用域中,因此在使用的时候可能导致很多的冲突.
namespace limou3434//后面是这块命名空间的名字
{
int print = 100;//在命名空间内定义一个变量
int function(int n)//在命名空间内定义一个函数
{
return n + 1;
}
struct Limou//在命名空间内定义一个结构体
{
int a;
char b;
float c;
double d;
};
namespace limou//在命名空间内嵌套一个命名空间
{
int e = 1;
int f = 2;
int g = 3;
}
}
int main()
{
return 0;
}
//命名空间名称::命名空间内的成员名字;
#include
namespace limou3434//后面是这块命名空间的名字
{
int print = 100;//在命名空间内定义一个变量
}
int main()
{
printf("%d\n", limou3434::print);
return 0;
}
//using 命名空间名称::命名空间内成员名字;
#include
namespace limou3434//后面是这块命名空间的名字
{
int print = 100;//在命名空间内定义一个变量
}
using limou3434::print;
int main()
{
printf("%d\n", limou3434::print);
return 0;
}
这样写不太好,这样会把所有标准库的名字全部暴露
using namespace limou3434;
using namespace std;//这里说明cout是std这块命名空间的成员之一
int main()
{
cout << print;
return 0;
}
#include
using namespace std;
int main()
{
int a = 0;
cin >> a;
cout << a << endl;
return 0;
}
声明或定义函数的时候为函数指定一个缺省值(默认值,这里是翻译问题,才叫缺省)。如果在使用函数的时候没有指定实参,就使用默认值为函数参数,否者使用指定的实参
#include
using namespace std;
void function(int a = 0)
{
if (a == 0)
{
cout << "你好!!!" << endl;
}
else
{
cout << "hello!!!" << endl;
}
}
int main()
{
function();
function();
function(1);
return 0;
}
//.h文件
voide function(int a = 100);
//.cpp文件
void function(int a = 50)
{
//某些具体代码
}
//在VS2022中哪怕重定义的缺省的值是一样的也不行
#include
using namespace std;
void fun(int a = 0);
int main()
{
fun();
return 0;
}
void fun(int a = 0)
{
if (a == 0)
{
printf("0\n");
}
else
{
printf("%d\n", a);
}
}
在现实生活中有的词语可以根据上下文语义,从而产生不同的意思,这就是一种重载的体现
#include
using namespace std;
void function(int a = 0)
{
if (a == 0)
{
cout << "你好!!!" << endl;
}
else
{
cout << "hello!!!" << endl;
}
}
char function(char b)
{
cout << b << endl;
return 1;
}
int main()
{
function();
function(1);
function('c');
return 0;
}
int a = 10;
int& b = a;
int x = 20;
b = x;//因此这个语句的意思是“x的值赋给b”,而不是“x成为了b的别名”,这跟指针就有很大的区别
有关重载的原理这里,涉及到的C++内容比较多,以后我再来进行补充。
引用不是新定义一个变量,而是为已存变量取个别名,引用变量不会开辟新的内存空间,它和它引用的变量共用同一块内存空间。
类型& 引用变量名(对象名) = 引用实体;
#include
int main()
{
int a = 10;//实际变量
int& a1 = a;//引用1
int& a2 = a;//引用2
printf("%d %d %d\n", a, a1, a2);
printf("%p %p %p", &a, &a1, &a2);
return 0;
}
int main()
{
const int a = 10;
//int& a1 = a;
const int& a1 = a;
10;
//int& b1 = 10;
const int& b1 = 10;
double c = 3.14159;
//int& c1 = c;
const int& c1 = c;
return 0;
}
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 0, y = 2;
Swap(x, y);
}
#include
int& function(int& x)//int& x = i,因此x是i的别名
{
x++;//这个x++等价于i++
return x;//返回x,int& ("function()") = x,因此函数甚至可以被赋值
}
int main()
{
int i = 0;
int j = 0;
j = function(i);
printf("%d\n", j);
printf("%d\n", function(i) = 10);
return 0;
}
//顺序表结构体
typedef struct SeqList
{
int* a;
int size;
int capacity;
}SL;
//初始顺序表
void SLPushInit(SL& s, int capacity = 4);
//尾插顺序表
void SLPushBack(SL& S, int x);
//修改顺序表
int& SLAt(SL& s, int pos);
{
assert(pos >= 0 && pos <=s.size);
return s.a[pos];
}
int main()
{
SL sl;
SLPushInit(sl);//初始化
SLPushBack(sl, 1);//尾插
SLPushBack(sl, 2);//尾插
SLPushBack(sl, 3);//尾插
SLPushBack(sl, 4);//尾插
for(int i = 0; i < sl.size; ++i)
{
cout << SLAt(sl, i) << endl;//输出顺序表的元素
}
SLAt(sl, 0)++;//拿到s.a[pos],对其进行++
SLAt(sl, 0) = 10;//拿到s.a[pos],修改成10
}
#include
using namespace std;
int main()
{
const int c = 20;//c可读不可写
//int& d = c;//d把c权限放大了(可读可写),这是不被允许的
const int& d = c;//这是允许的,属于权限平移的概念
int e = 30;
const int& f = e;//但是权限缩小是被允许的
int g = 1;
double h = g;//这里产生一个临时变量,将存储数据提升后的g,再赋予h(这里g用显式强转也不行,无论是显式还是隐式,都不会改变g本身的类型)
//double& i = g;//这是不被允许的,因为这里产生一个临时变量,将存储数据提升后的g,而这个临时变量具有常属性,临时变量被h引用的话发生了权限放大
const double& i = g;//这是被允许的,只是发生了权限平移
const double& j = 3.14;//这个也是被允许的,因此拥有const修饰的引用允许引用常量。所以如果是在函数形参处使用引用时,如果不需要改变值,就要尽可能使用const修饰
return O;
}
#include
using namespace std;
void function_1(int n)
{
;
}
void function_2(int& n)
{
;
}
void function_3(const int& n)
{
;
}
int main()
{
int a = 10;
const int b = 20;
function_1(a);
function_1(b);
function_1(30);
function_2(a);
//function_2(b);//这是不被允许的
//function_2(30);//这是不被允许的
function_3(a);
function_3(b);
function_3(30);
return 0;
}
指针更强大、更危险、更复杂/引用局限一些、更安全、更简单
这里可以查看汇编代码,基本没有区别
#include
using namespace std;
inline int add(int a = 0, int b = 0)//被inline修饰的函数
{
return a + b;
}
int main()
{
int c = 0;
c = add(3, 5);
cout << c;
return 0;
}
这一部分在Windows不好演示,就等我后续用Linux再补充吧……
在现代C++中,基本建议尽量使用const、enum、inline,而不使用宏
随着一个工程的扩大,程序中用到的类型也越来越复杂,经常体现在
/*没有使用tepedef重命名*/
#include
#include
int main()
{
std::map<std::string,std::string>m{ { "apple", "苹果" }, { "orange", "橙子" }, {"pear", "梨"} };
std::map<std::string, std::string>::iterator it = m.begin();
//其中std::map::iterator是一个类型,都是类型的名字太长了,容易写错,可以尝试使用typedef给这个类型取个别名
while (it != m.end())
{
//....
}
return 0;
}
/*没有使用tepedef重命名*/
#include
#include
int main()
{
std::map<std::string,std::string>m{ { "apple", "苹果" }, { "orange", "橙子" }, {"pear", "梨"} };
std::map<std::string, std::string>::iterator it = m.begin();
//其中std::map::iterator是一个类型,都是类型的名字太长了,容易写错,可以尝试使用typedef给这个类型取个别名
while (it != m.end())
{
//....
}
return 0;
}
#include
using namespace std;
int function()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = function();
cout << typeid(b).name() << " " << b << endl;
cout << typeid(c).name() << " " << c << endl;
cout << typeid(d).name() << " " << d << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
#include
using namespace std;
int main()
{
//一个变量
int x = 100;
//结合指针
auto a = &x;
auto* b = &x;
//结合引用
auto& c = x;
//测试类型和输出,typeid可以打印类型
cout << typeid(a).name() << " " << a << endl;
cout << typeid(b).name() << " " << b << endl;
cout << typeid(c).name() << " " << c << endl;
//修改变量
*a = 10;
*b = 20;
c = 30;
return 0;
}
#include
using namespace std;
int main()
{
//正确使用
auto a = 1, b = 2;
//错误使用
auto c = 3, d = 4.0;//该行代码会编译失败,因为c和d的初始化表达式类型不同
return 0;
}
这不是C++的首创,而是后面加进来的
//在C++98中,遍历一个数组可以按照以下的方式使用
#include
using namespace std;
//使用C++98遍历方式
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
//在C++11中可以使用基于范围的for循环。
//for后面的括号由冒号“:”分为两部分,第一部分是范围内用于迭代的变量,第二部分表示被迭代的范围
#include
using namespace std;
//使用C++11遍历方式
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
由于C++不支持直接传数组(这样消耗大,浪费)所以在函数传数组的时候必须提供begin和end方法,begin和end就是for循环迭代的范围(有关begin和end的具体使用后面再说)
void function(int arr[])//这个函数是不正确的,因为for的范围不确定
{
for(auto& e : arr)
cout << e << endl;
}
//NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
//在C++98中,字面量0既可以是一个整型数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看作整型常量,如果这么使用NULL时,就会具有一定的麻烦
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)//函数重载
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);//误用第一个函数,因为处理NULL的时候,NULL是被定义为0的
f((int*)NULL);//需要使用强制类型转换才可以使用第二个函数
return 0;
}
在C++中,可以使用函数来打印一个变量的类型
#include
using namespace std;
int main()
{
int a = 10;
double b = 3.14;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
return 0;
}
这个了解一下就行,以后我还会在带您仔细研究这个打印数据类型的原理的。