亲宝软件园·资讯

展开

C++命名空间 缺省参数 const总结 引用总结 内联函数 auto关键字详解

一只少年AAA 人气:0

命名空间

概述

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

举个例子:

#include <stdio.h>
#include <stdlib.h>

int rand = 10;

int main()
{
	printf("%d", rand);
	return 0;
}

程序编译的结果显示rand重定义了,为什么会这样呢?因为在stdlib.h这个头文件中已经定义了rand这样一个函数,这样就导致了编译器不知道这是一个函数还是一个变量,C语言中无法应对这种冲突,只能通过改名字来避免。

而C++为了解决这个问题,引入了命名空间的概念。

命名空间的定义

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

//命名空间
//A就是命名空间的名字
namespace A{
	int a;
	void func()
	{}
}

注意事项:

接下来给一个完整的代码块来展示命名空间的注意事项和使用:

#include<iostream> //引入头文件
#include<string>//C++中的字符串
using namespace std; //标准命名空间
//A就是命名空间的名字
namespace A
{	
	//命名空间内既可以定义变量也可以定义函数
	int rand = 10;
	int Sub(int x, int y)
	{
		return x + y;
	}
	struct ListNode
	{
		int data;
		struct ListNode* next;
	};
}
//命名空间的嵌套定义
namespace B
{
	int rand;
	namespace C
	{
		int rand;
	}
}
//命名空间是开放的,随时可以加入新成员,但是新成员只能在加入后使用
namespace B
{
	int c;//此时c会合并到命名空间B中,实际上就是个合并的过程
}
//匿名命名空间
namespace 
{
	int d = 5;//命名空间没有名字,就类似于static int d = 50,是个静态全局变量,别的文件无法使用
}
int main()
{
	//命名空间的使用
	//1.::作用域限定符
	//访问A空间的Sub函数
	cout << A::Sub(10, 20) << endl;
	//2.访问嵌套空间
	//访问B空间的C空间的rand变量
	B::C::rand = 5;
	cout << B::C::rand << endl;

	system("pause");
	return EXIT_SUCCESS;
}

using关键字

引入using关键字之后,命名空间的使用又变得不一样

namespace A
{
	int a = 10;
}
void test01()
{
	//using声明的意思就是让命名空间中某个标识符可以直接使用
	using A::a;
	cout<<a<<endl;
}

注意:

1.使用using声明,就不会每次写A::a了,直接用a就可以

2.using A::a声明的意思就是把变量a又在test函数中定义了一次,此时如果在test内部再定义一个int a;就会出错

namespace A
{
	int a = 10;
}
using namespace A;
void test01()
{
	cout<<a<<endl;
}

使用using关键字修饰namespace整个命名空间,实际上就是脱去了这个命名空间的外衣,就等价于你定义了一个int a在全局

思考一个问题:下面代码有错吗?

在test01函数体内又定义了一个int a,会报错么?如果不报错,那么输出的是全局的 a = 10 还是局部的a = 20?

namespace A
{
	int a = 10;
}
using namespace A;
void test01()
{
	int a = 20;
	cout<<a<<endl;
}

答案是不会报错,输出的是局部的20,因为命名空间A内部的变量a在使用using关键字后相当于在全局定义了一个int a ;而在函数体内定义一个局部的 int a;两个变量的作用域不同,是可以定义同名变量的,输出的是局部变量的值,小伙伴的要注意区分~

C++输入和输出

#include <iostream>
using namespace std;// 将std标准命名空间进行展开

int main()
{
	cout << "hello world" << endl;
	// std::cout << "hello world" << endl; 也可以这样写就不展开std标准命名空间
	return 0;
}

使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%c

int main()
{
	int a = 0;
	double b = 0.0;

	cin >> a;
	cin >> b;

	cout << "a = " << a << " b = " << b << endl;
	system("pause");
	return EXIT_SUCCESS;
}

运行结果如下:

缺省参数

概念:

缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

void PrintNum(int n = 0)
{
	cout &lt;&lt; n &lt;&lt; endl;
}

int main()
{
	PrintNum();// 无参数时,使用参数的默认值
	PrintNum(10);// 有参数时,使用指定的实参
	system("pause");
	return EXIT_SUCCESS;
}

全缺省参数

参数都要一个默认值,给定的实参依次从左向右给形参赋值

注意:我们在调用函数时,只能缺省最右边的若干个参数,形如:Fun(4, , 6);这种调用是错误的调用方法。

void Func(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}


int main()
{
	// 实参从左向右一次给形参赋值
	Func();
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	system("pause");
	return EXIT_SUCCESS;
}

运行结果如下:

半缺省参数

只有部分形参给定了默认值,半缺省参数必须从右往左依次来给出,不能间隔着给。

值得注意的是,缺省参数只能为最右边的若干个

形如:void Fun(int a=10, int b, int c = 30) { }这样的语句是错误的用法。

形如:Fun(1, ,3)这种调用也是错误的。

void Func(int a, int b = 10, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	system("pause");
	return EXIT_SUCCESS;
}

运行结果如下:

注意:

const限定符

const修饰符的作用

规则

分类

  常变量:  const 类型说明符 变量名

  常引用:  const 类型说明符 &引用名

  常对象:  类名 const 对象名

  常成员函数:  类名::fun(形参) const

  常数组:  类型说明符 const 数组名[大小]    

  常指针:  const 类型说明符* 指针名 ,类型说明符* const 指针名

const全局/局部变量

C

在C语言中const修改全局变量是存储在全局区(即静态存储区),修饰局部变量时存储在栈区

//const修饰的常量
const int a = 10;//全局const常量,放在常量区,受到常量区的保护
void test01()
{
	//直接修改失败
	a = 100;
	//间接修改失败
	int *p = &a;
	*p = 100;
}
//局部conts修饰常量
void test02()
{
	conts int b = 10;//数据放在栈区,是个伪常量
	//直接修改失败
	b = 100;
	//间接修改成功
	int *p = &b;
	*p = 100;
}

总结:

C++

在C++中编译器会自动优化,会将常量的数值直接替换(类似于宏定义),这导致了const局部变量与真实值产生了不一致。(常量折叠现象),而C语言会先去内存中寻找,然后替换

举个例子:

const int aa = 10;//没有内存
void test01()
{
	cout << aa << endl;//在编译阶段,编译器会自动优化,将aa直接替换成常量10
	const int bb = 20;//栈区
	int *p = (int *)&bb;
	*p = 200;
	cout << bb << endl;//输出的还是20,还是那句话,在编译阶段代码中的bb就已经全部被替换成20,此时其实输出的是这样的cout << 20 << endl;但是变量bb此时的值已经被改变了,变成了200,但是由于编译器优化,造成了常量折叠现象
}

总结:

编译器不能优化的情况

例子一:用变量给const修饰的局部变量赋值

void test03()
{
	int a =10;
	const int b = a;
	int *p = (int *)&b;
	*p = 100;
	cout << b << endl;//输出100
}

例子二:利用关键字阻止优化

void test04()
{
	const volatile int a = 7;
	int *p = (int *)(&a);
	*p = 8;
	cout << "a=" << a << endl;//输出8
	cout << "*p=" << *p;
	system("pause");
	return 0;
}

例子三:自定义数据类型不能优化

struct Maker
{
	Maker()
	{
		a = 100;
	}
	int a;
};
void test05()
{
	const Maker ma;
	cout << ma.a <<endl;
	Maker *p = (Maker*)&ma;
	p->a = 200;//可以修改ma中的值
	cout << ma.a << endl;
}

const修饰指针和引用

const修饰指针

涉及到两个很重要的概念,顶层const底层const

从 const 指针开始说起。const int* pInt; 和 int *const pInt = &someInt;,前者是 *pInt 不能改变,而后者是 pInt 不能改变。因此指针本身是不是常量和指针所指向的对象是不是常量就是两个互相独立的问题。用顶层表示指针本身是个常量,底层表示指针所指向的对象是个常量。更一般的,顶层 const 可以表示任意的对象是常量,这一点对任何数据类型都适用;底层 const 则与指针和引用等复合类型有关,比较特殊的是,指针类型既可以是顶层 const 也可以是底层 const 或者二者兼备。

int a = 1;
int b = 2;
const int* p1 = &a;//指针常量(顶层const)
int* const p2 = &a;//常量指针(底层const)
1.指针常量(指针不可改,指针指向的对象可改)

int a = 10;
int b = 5;
int * const p1 = &a;
p1 = &b; //指针不可改,不合法
*p1 = b; //指针指向的对象可改,合法

2.常量指针(指针可改,指针指向的对象不可改)
int a = 10;
int b = 5;
const int* p2 = &a;
p2 = &b; //指针可改, 合法
*p2 = b; //不合法

拷贝与顶层和底层 const

int i = 0;
int *const p1 = &i;     //  不能改变 p1 的值,这是一个顶层
const int ci = 42;      //  不能改变 ci 的值,这是一个顶层
const int *p2 = &ci;    //  允许改变 p2 的值,这是一个底层
const int *const p3 = p2;   //  靠右的 const 是顶层 const,靠左的是底层 const
const int &r = ci;      //  所有的引用本身都是顶层 const,因为引用一旦初始化就不能再改为其他对象的引用,这里用于声明引用的 const 都是底层 const

const修饰引用

常引用所引用的对象不能更新,使用方法为:const 类型说明符 &引用名

非const引用只能绑定非const对象,const引用可以绑定任意对象,并且都当做常对象

常引用经常用作形参,防止函数内对象被意外修改。对于在函数中不会修改其值的参数,最好都声明为常引用。复制构造函数的参数一般均为常引用

class Example{
public:
    Example(int x, int y):a(x),b(y){}
    Example(const Example &e):a(e.a),b(e.b){} //复制构造函数
    void print();
    void print() const;
private:
    const int a,b;
    static const int c = 10;
};
void Example::print() {cout<<"print():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}

const修饰函数参数

const修饰参数是为了防止函数体内可能会修改参数原始对象。因此,有三种情况可讨论:

void Fun(const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数

void func (const int& n)
{
     n = 10;        // 编译错误 
}

const修饰函数返回值

const修饰函数返回值的含义和用const修饰普通变量以及指针的含义基本相同。这样可以防止外部对 object 的内部成员进行修改。

const int* func()   // 返回的指针所指向的内容不能修改
{
    // return p;
}

const成员函数和数据成员

类的常成员函数

由于C++会保护const对象不被更新,为了防止类的对象出现意外更新,禁止const对象调用类的非常成员函数。因此,常成员函数为常对象的唯一对外接口。

常成员函数的声明方式:类型说明符 函数名(参数表) const

class A
{
public:
	//返回值的类型是int &类型
    int& getValue() const
    {
        // a = 10;    // 错误
        return a;
    }

private:
    int a;            // 非const成员变量
};

注意事项:

类的常数据成员

类的数据成员不能在任何函数中被赋值或修改,但必须在构造函数中使用初始化列表的方式赋初值,因为const修饰的对象必须初始化。
举个例子,刚才的类如果a, b为常数据成员,则应该改写为如下形式:

class Example{
public:
    Example(int x, int y):a(x),b(y){} //初始化列表方式赋初值
    void print();
    void print() const;
private:
    const int a,b;
};
void Example::print() {cout<<"print():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}

如果为静态常数据成员,由于不属于具体对象,所以不能在构造函数里赋值,仍然应该在类外赋值。特别地,如果静态常量为整数或枚举类型,C++允许在类内定义时指定常量值。
比如以下两种方式均合法:

class Example{
public:
    Example(int x, int y):a(x),b(y){}
    void print();
    void print() const;
private:
    const int a,b;
    static const int c = 10; //静态常量
};
class Example{
public:
    Example(int x, int y):a(x),b(y){}
    void print();
    void print() const;
private:
    const int a,b;
    static const int c; //静态常量
};
const int Example::c = 10;

const修饰类对象

用const修饰的类对象,该对象内的任何成员变量都不能被修改。
因此不能调用该对象的任何非const成员函数,因为对非const成员函数的调用会有修改成员变量的企图。

class A
{
 public:
    void funcA() {}
    void funcB() const {}
};
int main
{
    const A a;
    a.funcB();    // 可以
    a.funcA();    // 错误

    const A* b = new A();
    b->funcB();    // 可以
    b->funcA();    // 错误
}

const与宏定义的区别

(1) 编译器处理方式不同
  define宏是在预处理阶段展开。
  const常量是编译运行阶段使用。

(2) 类型和安全检查不同
  define宏没有类型,不做任何类型检查,仅仅是展开。
  const常量有具体的类型,在编译阶段会执行类型检查。

(3) 存储方式不同
  define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
  const常量会在内存中分配(可以是堆中也可以是栈中)。

(4)作用范围不同

​ const有作用域,而define不重视作用域,默认定义处到文件结束,如果定义在指定作用域下有效的常量,那么define不能用。

(5)const 可以节省空间,避免不必要的内存分配。 例如:

  #define PI 3.14159 //常量宏  
    const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......  
    double i=Pi; //此时为Pi分配内存,以后不再分配!  
    double I=PI; //编译期间进行宏替换,分配内存  
    double j=Pi; //没有内存分配  
    double J=PI; //再进行宏替换,又一次分配内存!  

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。

const与static的区别

static

1、static局部变量 将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中

2、static 全局变量 表示一个变量在当前文件的全局内可访问

3、static 函数 表示一个函数只能在当前文件中被访问

4、static 类成员变量 表示这个成员为全类所共有

5、static 类成员函数 表示这个函数为全类所共有,而且只能访问静态成员变量

static关键字的作用

(1)函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值
(2)在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问
(3)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝
(4)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量

const关键字的作用

(1)阻止一个变量被改变
(2)声明常量指针和指针常量
(3)const修饰形参,表明它是一个输入参数,在函数内部不能改变其值
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为”左值”

引用

概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。也就好比我们给同学取了一个外号一样。
用法: 类型&引用变量名(对象名)= 引用实体;

int main()
{
	int a = 10;
	int& ra = a;// ra是a的引用

	cout << a << endl;
	cout << ra << endl;

	system("pause");
	return EXIT_SUCESS;
}

特性及注意事项

Type& ref = val;

注意事项:

int a = 10;
int b = 20;

//int& ra;//引用必须初始化

int& ra = a;
int& rra = a;

//int& ra = b;//引用一旦初始化,不能改变它的指向
//int& raaa = NULL;//NULL本身就是不合法的,不能绑定不合法的空间

数组的引用

int arr[] = {1, 2, 3, 4, 5};
//三种方式
//1.定义数组类型
typedef int(MY_ARR)[5];
MY_ARR &arref = arr;//建立引用,int &b = a

//2.直接定义引用
int(&arref2)[5] = arr;

//3.建立引用数组类型
typedef int(&MY_ARR3)[5];
MY_ARR3 arref3 = arr;

引用的使用场景

//1.作为函数的参数
void func(int &a, int &b)
{
	cout << a+b << endl;
}
//2.引用作为函数的返回值
int& func2()
{
	int b = 10;//不能返回局部变量的引用
	int &p = b;
	return p;//错误的
}
int& func3()
{
	static int b = 10;
	return b;
}
void test01()
{
	int& q = func2();
	cout << q << endl;
	q = 100;
	cout << q << endl;

	func2() = 200;
	cout << q << endl;
	cout << func2() << endl;
//--------------上面的代码都是错误的,这里解释一下
//int& q = func2();实际上就是int&q = p,但是fun2函数执行完了之后,局部变量全部被销毁了,所以int&q = 就指向了一个非法的区域,但是编译器没有检测出来,具体原因是什么不清楚
	func3() = 100;
	cout << func3() << endl;
}

引用做返回值时,一般分两种情况:返回的对象未归还系统和返回的对象归还系统。如果返回对象不归还系统,我们可以引用返回,否则就需要传值返回

想要返回局部变量:1.把局部变量写在堆区 2.把局部变量写在静态区

常引用

注意:

//普通引用
int a = 10;
int &ref = a;
ref = 20;
//int &ref2 = 10不能给字面量取别名,因为10这个字面量在内存中没有空间,存储在寄存器中

//常引用
const int &ref3 = 10;//可以给const修饰的引用赋予字面量
//编译器会把上面的代码变为:int tmp = 10;const int &ref3 = tmp;

内联函数

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。

inline int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int z = Add(1, 2);
	return 0;
}

如果加了inline关键字,编译器在编译期间会用函数体替换函数的调用(类似于宏定义,在编译阶段就替换函数调用,将函数调用直接展开,减少了调用的时间,但是空间消耗巨大)。

下面是内敛函数的几个特性:

1.inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2.inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联(是否称为内联函数由编译器决定)。
3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

例子:一个相同的函数,一个加了inline 关键字,一个没加,加上一个函数要执行10条指令,文两个函数分别调用1000次要执行多少条指令?
普通函数:1000+10(一次调用1次指令,加起来就是1000条,每次调用都是call函数,函数不展开就是10条)
内联函数:1000*10条指令(展开就是每次调用都是10条指令)
所以说,内联函数展开,会让程序变大,所以代码很短的函数可以考虑有内联,长函数和递归函数不适合用内联。

auto关键字

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

int a = 10;
auto b = a;// 自动推导b的类型为int
auto c = 'c';// 自动推导类型为char

cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
//auto d;必须初始化

有一下几种用法:

int a = 10;
// auto和auto*无区别
auto pa1 = &a;
auto* pa2 = &a;

auto& ra = a;// ==> int& ra = a;

cout << typeid(a).name() << endl;
cout << typeid(pa1).name() << endl;
cout << typeid(pa2).name() << endl;
cout << typeid(ra).name() << endl;

运行结果如下:

auto a = 3, b = 4;
auto c = 3.4, d = 5.5;
auto i =0, *p = &i;//正确,i是整型,p是整型指针

cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(i).name() << endl;
cout << typeid(p).name() << endl;

auto不能推导的两个常见

加载全部内容

相关教程
猜你喜欢
用户评论