c/c++小知识点

  • strcpy函数



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

int main()
{
    char strDst[10];
    char *strSrc = "0123456789";
    strcpy(strDst, strSrc);
    printf("strDst is %s\n", strDst);
    return 0;
}
//结果
//strDst is 0123456789
//Abort trap: 6
出现该错误的原因为:strcpy会导致数组越界
1.在c语言中,string可以用作变量名。C++不可以,string是关键字
2.strcpy的意思是:把字符串strSrc中的内容拷贝到string中,连字符串结束标志'\0'也一起拷贝,这样strSrc在内存中的存放为0123456789+\0,所需的空间为11个字节,而string是10个字节,因此会存在数组越界的情况

编写一个标准的strcpy函数

char * strcpy(char *strDest, const char *strSrc)//将源字符加const,表明其为输入参数,加2分
{
    //对源地址和目的地址加非空判断,加3
    assert((strDest != NULL) && (strSrc != NULL));
    char * address = strDest;
    while((*strDest++ = *strSrc++) != '\0');
    return address; // 为了实现链式操作,将目的地址返回,加3分?
}
// strcpy返回目标串的地址,这个返回值的目的是可以使strcpy用在链式表达式中(链式就是一连串写下来的意思。。。),增加灵活性
// 比如
// char s1[]="12345";
// char s2[100];
// int len;
// len=strlen(strcpy(s2,s1+1)); //从s1的第二个字符开始复制内容到s2,并且计算出s2的长度
  • malloc



malloc分配完内存后:

1.注意判空
2.一定要free
3.将该指针设为NULL,不然会变成野指针
#include <stdlib.h>
#include <stdio.h>
#include <string>
void GetMemory(char **p, int num)
{ 
    if(num<=0) {
        printf("申请的内存空间要大于零!\n");
    }
    *p = (char*)malloc(num);
    if(*p==NULL) {
        printf("申请内存失败!\n");
    }
}

int main()
{
    char *str = NULL;
    GetMemory(&str, 100); // 参数传递使用地址或引用
    strcpy(str, "hello world");
    printf("%s\n", str);
    free(str);
    str = NULL;
    return 0;
}
  • 变量与0比较



BOOL型变量:
 if (!var)
int型变量:
 if (0 == var)
float型变量:
 const float EPSINON = 0.00001;
 if (var >= -EPSINON && var <= EPSINON)
指针变量:
 if (NULL == var)
  • 数组名



#include <iostream>
using namespace std;
void fun(char *str)
{
    cout << "fun(str) sizeof: " << sizeof(str) << endl;//结果:8
}

int main()
{
    char str[10];
    char *ptrStr[10]; //指针数组:每一个元素均为指针
    char* pStr = str;
    cout << "sizeof(str): \t" << sizeof(str) << endl; //结果:10
    cout << "sizeof(ptrStr): \t" << sizeof(ptrStr) << endl; //结果:10*8 = 80
    cout << "sizeof(pStr): \t" << sizeof(pStr) << endl;//结果:8
    //  "\t"表示tab键
    fun(str);
    return 0;
}

【剖析】

fun(char *str)函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

【数组名的本质如下:】

(1)数组名指代一种数据结构,这种数据结构就是数组;
例如:
char str[10]; cout << sizeof(str) << endl; 输出结果为10,str指代数据结构char[10]。
(2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
char str[10];
str++; //编译出错,提示str不是左值

(3)数组名作为函数形参时,沦为普通指针。
64位平台下,指针的长度(占用内存的大小)为8字节,故sizeof(str) 、sizeof(pStr)都为8。
// 64位系统,这个位数指的是CPU里面的通用寄存器的数据宽度为64位,也就是说一个地址占二进制位数是64,所以sizeof(double*)==sizeof(int*)==sizeof(char*)==64位/8==8字节
// 32位系统,同理,他的一个地址占32位二进制空间,sizeof(double *)==sizeof(int *)==sizeof(char *)==32/8==4
// Bit意为"位"或"比特",是计算机运算的基础;
// Byte意为"字节",是计算机文件大小的基本计算单位;
// 1byte=8bits,两者换算是1:8的关系。
// 两个字节一个汉字。
// 1Bit=1/16个字
// 所以16bit=1个汉字
  • 函数形参



void GetMemory(char *p) {/*改变p的值*/}
void GetMemory_1(char **p) {/*改变p的值*/}
void GetMemory_2(char *&p) {/*改变p的值*/}
char *str = NULL;
GetMemory(str);//传入形参并不能改变形参的值
GetMemory_1(&str);//传地址,可以改变形参的值
GetMemory_2(str);//传引用,可以改变形参的值

要改变一个变量的值,要传地址,例如你改变int a的值,你传&a,改变 int *a 你就指针的地址,也就是二级指针
或者使用引用调用

  • 局部变量作为函数返回值



char *GetMemory(void)
{
    char p[] = "hello world!";
    return p;
}
//上述用法错误
//p是一个数组名,属于局部变量,存储在栈中,在函数结束后,内存被释放,函数返回的指向p的内容也不确定
//p局部变量存储在动态存储区,在函数调用时动态分配内存,调用完成后销毁
// 可以这样修改
//1:
char *p = "hello world!";
return p; //函数返回p存储的地址
//2:
static char p[] = "hello world!";
return p;// 改为静态变量,存储在静态存储去,在程序结束后释放

1.返回值为值类型的函数

int func()
{
    return 1;
}
int i = func();
//有一个临时对象来保存func()函数的返回值1,之后将临时对象的值赋给i。
//编译器将所有的临时对象自动成为const。所以,对于返回值为值类型的函数,其返回值func()为右值。

2.返回值为引用类型的函数

当函数的返回值是引用类型时,其返回值即为return的变量,所以不需要临时对象保存其返回值。所以,对于返回值为引用类型的函数,其返回值为左值。

#include<stdlib.h>
#include<stdio.h>
#include<string>
char& get_val(std::string& str, int index)
{
    return str[index - 1];
}
int main()
{
    std::string str = "12345";
    char ch = get_val(str, 1);
    get_val(str, 1) = 'a';
    printf("ch is %c\n", ch);
    printf("str is %s\n", str.c_str());
    return 0;
}
// 结果
// ch is 1
// str is a2345

3.返回值是const内置类型时

对于返回值是内置类型的函数来说,即使是非const类型,其返回值也不会被修改。无论是否有const修饰,其函数的返回值都是右值。

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

char get_val(std::string& str, int index)
{
    return str[index - 1];
}

const char get_val2(std::string& str, int index)
{  
return str[index - 1];  
}

int main()
{
    std::string str = "12345";
    char ch = get_val(str, 1);//ch的值是'1'
    get_val(str, 1) = 'a';//编译时报错,expression is not assignable: 左操作数必须为左值
    return 0;
}

4.返回值是自定义类型时

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

class X {
    int x;
public:
    X(int i = 0);
};
X::X(int ii)
{  
    x = ii;
}
X f1()
{  
    return X();
}  
const X f2()
{  
    return X();
}

int main()
{
    f1() = X(1);//编译时不报错
    f2() = X(1);//error: no viable overloaded '=': 没有找到接受“const X”类型的左操作数的运算符
    return 0;
}
// 函数f1()的返回值是非const类型,所以可以对其返回值进行赋值,即返回值是左值;而f2()的返回值是const类型,则不能对其进行赋值,即返回值是右值。
// 对非const类型的返回值进行赋值,虽然可以编译通过,但是实际上这么做没有意义。因为函数的返回值保存在临时对象中,我们无法访问该临时对象,在该行代码执行完毕后,临时对象就会被清除。

指向常量的指针

指向常量的指针不能用于改变其所指向对象的值。想要存放常量对象的地址,只能使用指向常量的指针。

const double pi = 3.14;
double *ptr = &pi;//错误,ptr是非const指针
const double *cptr = &pi;//正确

常量指针

常量指针的本身是常量,必须初始化,而且一旦初始化,则它本身的值就不会改变。

int errNumb = 0;
int* const curErr = &errNumb;//curErr是常量指针,且一直指向errNumb

指向常量的常量指针
指向常量的常量指针指向一个常量对象,且该指针本身也是常量。

const double* const pip = &pi;

5.返回值是指向常量的指针的函数

const char* get_string()
{
    return "12345";
}
// 函数get_string()返回从字符串字面值中建立的const char*。在编译器建立了该字符串并且将其存储在静态存储区之后,该返回值返回的是该字符串字面值在静态存储区中的地址。
// 所以get_string()函数的返回值是右值,且不能为非const指针赋值。

get_string()[0] = 'a'; //错误,表达式必须是可修改的左值
char* pstring = get_string();//错误,“const char*”类型不能用于初始化“char*”类型的实体
const char* cpstring = get_string();//正确
// 另外还需要注意的是,函数不能返回指向局部栈变量的指针,因为栈变量在函数返回后就销毁了,其返回的地址为无效地址。

6.返回值是指向常量的常量指针的函数

// 将“5返回值是指向常量的指针的函数”中提到的get_string()const char* const get_string()  
const char* const get_string()  
{  
     return "12345";
}

// 因为该返回值是指向常量的,所以像在“5返回值是指向常量的指针的函数”中提到的一样,该值为右值,不能对其进行赋值,也不能将其赋值给非const指针。
// const char* const ccpstring = get_string();//正确
// const char* cpstring = get_string();//正确
// 从上面代码可知,指向常量的常量指针可以赋值给指向常量的非常量指针
  • 文件包含和宏



文件包含

#include <> :按系统指定的目录检索,常用于包含库函数的头文件,用#include ""也能找到,但增加了编译时间
#include "":常用于包含自定义的头文件。先在.cpp文件所在目录去搜索包含的.h文件,用#include <>也能找到,但增加了编译时间
./ 是当前目录
../ 是父级目录
/ 是根目录

预编译宏

// 预编译宏
#ifndef XXXXX_H
#define XXXXX_H
#endif
// 作用是防止被重复引用

extern "C"

C++支持重载,而c语言不支持,原因?

因为函数被C++编译后在symbol库中的名字与c语言不同。
例如void fun(int x, int y);
C编译器编译后在symbol库中的名字为_fun,C++编译器则会产生像_fun_int_int之类的名字。_fun_int_int这种名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,实现C++代码调用其他C语言代码。
#ifdef __cplusplus // 告诉编译器,如果定义了__cplusplus(即如果是cpp文件,因为cpp文件默认定义了该宏),
extern "C"{ // 告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#endif
/*…*/
#ifdef __cplusplus
}
#endif

宏定义

#define 宏名 一段符号
示例:#define PI 3.141926

为什么使用宏定义?
方便修改和管理,不用一个一个的去改,也是一种参数化的思想。宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错,注意如果是表达式,一定用要用括号括起来。

C编译器,提供了几个特殊形式的预定义宏,在实际编程中可以直接使用,很方便。

__FILE__ 宏所在文件的源文件名
__FUNCTION__ 宏所在函数名
__LINE__ 宏所在行的行号
__DATE__ 代码编译的日期
__TIME__ 代码编译的时间

原文链接: https://www.cnblogs.com/vivian187/p/13347717.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    c/c++小知识点

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/397064

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月7日 上午9:14
下一篇 2023年4月7日 上午9:15

相关推荐