
C++基础入门
目录
C++基础入门
●1.命名空间
●2.输入与输出
●3.缺省参数
●4.函数重载
●5.C++支持函数重载的原理--名字修饰
●6.引用
●7.内联函数
●8.auto关键字(C++11)
●9.基于范围的for循环(C++11)
●10.指针空值nullptr(C++11)
1.命名空间
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
示例:
c++
#include <iostream>
#include <cstdlib> //rand()
// int rand = 10; error
namespace owndefine
{
int rand = 10;
}
typedef struct ListNode
{
struct ListNode *next;
int data;
} lnode;
namespace function1
{
typedef struct ListNode
{
struct ListNode *next;
int data;
} lnode;
}
namespace function2
{
int Add(int x, int y)
{
return x + y;
}
}
namespace function3
{
namespace function4
{
int Add(int x, int y)
{
return x + y;
}
}
}
namespace preference_factor
{
int a = 10;
int b = 20;
}
int main()
{
std::cout << "hello world" << std::endl;
//1.示例一:
// using namespace std;
// std::cout << rand << std::endl; error
// test.cpp:5:5: error: 'int rand' redeclared as different kind of symbol
// int rand=10;
// ^
// In file included from /usr/include/c++/4.8.2/cstdlib:72:0,
// from test.cpp:2:
// /usr/include/stdlib.h:374:12: error: previous declaration of 'int rand()'
// extern int rand (void) __THROW;
using namespace std;
//正确解决冲突
std::cout << owndefine::rand << std::endl;
std::cout << rand << std::endl;
// 2.示例二:
lnode *l1 = new lnode();
cout << "lnode* l1=new lnode()" << endl;
function1::lnode *l2 = new function1::lnode();
cout << "function1::lnode* l2=new function1::lnode()" << endl;
// 3.示例三:嵌套命名空间
cout << function2::Add(1, 2) << endl;
cout << function3::function4::Add(1, 2) << endl;
// 4.示例四:命名空间参数变量使用
cout << preference_factor::a << endl;
cout << preference_factor::b << endl;
using preference_factor::a;
cout<<a<<endl;
using preference_factor::b;
cout<<b<<endl;
using namespace preference_factor;
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
2.输入与输出
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
- cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含头文件中。
- <<是流插入运算符,>>是流提取运算符.(系统编程部分根据文件的重定向技术做细致了解)
- 使用C++ 输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。(可以这样理解,cout/cin就是两个类,类的内部封装了printf/scanf函数接口,类内并对其进行了相应的优化)
- cout和cin分别是ostream和istream类型的对象。
#include库函数中输入输出定义如下:
示例:
c++
#include <iostream>
int main()
{
//命名空间std(namespace std _GLIBCXX_VISIBILITY(default));
//在一般代码练习中我们一般将该命名空间展开,而在项目开发中我们为了避免冲突情况的发生,一般不会展开;
int data;
std::cin>>data;
std::cout<<data<<std::endl;
return 0;
//其余关于cout/cin的输入输出流操作,结合手册cplusplus进行使用;
}
3.缺省参数
- 半缺省参数必须从右往左依次来给出,不能间隔着给;
- 缺省参数不能在函数声明和定义中同时出现;
c++
*//a.h
void Func(int a = 10); //声明
// a.cpp
void Func(int a = 20) //定义
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该
用那个缺省值。*
- 缺省值必须是常量或者全局变量;
- C语言不支持(编译器不支持);
示例:
c++
#include <iostream>
using namespace std;
void function1(int a = 10)
{
cout << a << endl;
}
void function2(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
void function3(int a, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
// 示例一
function1();
function1(20);
// 示例二(全缺省)
function2();
// 示例三(半缺省)
function3(10);
return 0;
}
4.函数重载
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数/类型/类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
示例:
c++
#include <iostream>
using namespace std;
//参数类型
void Add(int a,int b)
{
cout<<"void Add(int a,int b)"<<endl;
}
void Add(double a,double b)
{
cout<<"void Add(double a,double b)"<<endl;
}
//参数个数
void func()
{
cout<<"void func()"<<endl;
}
void func(int x)
{
cout<<"void func(int x)"<<endl;
}
//参数顺序
void print_char(int a,char b)
{
cout<<"void print_char(int a,char b)"<<endl;
}
void print_char(char a,int b)
{
cout<<"void print_char(char a,int b)"<<endl;
}
int main()
{
// 示例一
Add(1,2);
Add(1.1,2.2);
// 示例二
func();
func(10);
// 示例三
print_char(1,'a');
print_char('a',1);
return 0;
}
5.C++ 支持函数重载的原理--名字修饰
在C/C++ 中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】
采用C语言编译器编译后结果:
--结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
采用C++编译器编译后结果:
--结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
6.引用
示例:
c++
#include <iostream>
#include<time.h>
//时钟函数接口:clock();
//size_t start=clock();~size_t end=clock();
using namespace std;
template <class T>
void swap(T &a,T &b)
{
int tmp=a;
a=b;
b=tmp;
}
int& count()
{
static int cnt=0;
cnt++;
//...
return cnt;
}
int main()
{
// 示例一
int a = 10;
int& ra = a;
cout << a << endl;
cout << ra << endl;
// 示例二(应用特性)
int b = 10;
// int &rb;//未初始化error
int& rb = b;
int& rrb = b;
cout << rb << endl;
cout << rrb << endl;
//示例三(常引用)
const int a=10;
// int &ra=a;//error,a为常量
// int &ra=10;//error,10为常量
const int& ra=a;
const int& rb=10;
double d=10.1;
//int &rd=d;//error,类型错误
const int& rd=d; //特殊点
//使用场景
//1.参数
int x=10;
int y=20;
cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
swap(x,y);
cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
//2.做返回值
int& ret=count();
cout<<ret<<endl;
return 0;
}
传值和传引用的比较:以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。而对于传引用而言,引用的是同一块空间,所以其效率较高。(测试代码如下)
参数和引用的作为返回值类型的性能比较:
值和引用的作为返回值类型的性能比较:
引用和指针的区别:
c++
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int &ra = a;
int *p=&a;
cout << "&a=" << &a << endl;//0x7ffcfcff1134
cout << "&ra=" << &ra << endl;//0x7ffcfcff1134
//引用在其语法概念上,就是起了一个别名而没有独立空间,和其引用实体公用一个空间;
cout<<"&p="<<&p<<endl;//0x7fffb115edb8
//而在底层实现上实际上是有空间的,因为引用是按照指针方式来实现的,通过汇编指令可以看出
return 0;
}

- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求。(引用必须初始化,否者就会报错;而指针可以指向确定空间也可无指向,即空指针)
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
- 没有NULL引用,但有NULL指针。
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位平台下占8个字节)
- 引用自加即引用的实体内容自加,指针自加即指针向后偏移一个类型的大小。
- 有多级指针但没有多级引用。
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
- 引用比指针使用起来相对更安全。(指针所指向开辟在堆上的空间,如果不对其进行释放,则可能会造成内存泄露)
7.内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数在一定程度上可以提高程序运行的效率。 不是使用了内联函数就会提高程序执行效率,例:一个函数需要调用10000次,使用内联和不使用内联的效率分析。调用函数将会在栈空间中保留一次函数栈帧,函数直接call该栈帧地址即可;若是使用内联函数,则将会在代码中展开10000次调用函数代码,这样的话将会极大地增大程序执行开销。我们的编译器是智能的,即使你写了inline它也将会再合适的时候进行展开,在合适的时候进行调用从而保证程序的执行效率。需要注意的是,inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
8.auto关键字(C++ 11)
C++11引入了auto关键字,用于自动推断变量的类型。auto关键字可以在变量声明时,根据变量的初始值自动确定变量的类型。
示例1:
c++
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef map<string, string> Map;
typedef map<string, string>::iterator map_iter;
int main()
{
// auto关键字使用的一些场景
// 1.类型难于拼写
// 2.含义不明容易导致错误
map<string, string> m = {
{"apple", "苹果"}};
map<string, string>::iterator m_p = m.begin();
while (m_p != m.end())
{
}
// 3.使用typedef进行替换
Map m1 = {
{"pear", "梨子"}};
map_iter m_p1 = m1.begin();
while (m_p1 != m1.end())
{
}
// 4.使用auto
Map m2 = {
{"apple", "苹果"}};
auto m_p2 = m2.begin();
while (m_p2 != m2.end())
{
}
return 0;
}
示例2:
c++
#include <iostream>
using namespace std;
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
// auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}

auto的使用细则:
- auto与指针和引用结合起来使用用auto声明指针类型时,用auto和auto没有任何区别,但用auto声明引用类型时则必须加&;
c++
void TestAuto()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;//Pi int*
cout << typeid(b).name() << endl;//Pi int*
cout << typeid(c).name() << endl;//i int
}
- 在同一行定义多个变量当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量;
c++
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
- auto不能作为函数的参数;
c++
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
- auto不能直接用来声明数组;
c++
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
- 为了避免与C++ 98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法;
- auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用;
9.基于范围的for循环(C++ 11)
C++11引入了基于范围的for循环(range-based for loop),它是一种更简洁和直观的循环语法,用于遍历容器或数组中的元素。
示例:
c++
#include <iostream>
using namespace std;
int main()
{
int arr[]={0,1,2,3,4,5,6,7,8,9};
for(auto e:arr)
cout<<e<<" ";
cout<<endl;
//0 1 2 3 4 5 6 7 8 9
for(auto &e:arr)
e*=2;
for(auto e:arr)
cout<<e<<" ";
cout<<endl;
//0 2 4 6 8 10 12 14 16 18
//与普通循环类似,也可以使用continue和break
return 0;
}
10.指针空值nullptr(C++ 11)
C++ 11引入了一个新的关键字nullptr,用于表示空指针。在旧版本的C++中,使用0或者NULL来表示空指针,这种方式可能对于重载函数或者模板有一些歧义。nullptr的引入解决了这个问题。
示例:
c++
#include <iostream>
void foo(int* ptr) {
if (ptr == nullptr) {
std::cout << "指针为空" << std::endl;
} else {
std::cout << "指针不为空" << std::endl;
}
}
int main() {
int* ptr = nullptr;
foo(ptr); // 输出:指针为空
int value = 10;
int* ptr2 = &value;
foo(ptr2); // 输出:指针不为空
return 0;
}
<您的三连和关注是我最大的动力>
🚀 文章作者:张同学的IT技术日记
分类专栏:C++系列