数组与字符串长度——C语言经典方法(没写完!)

文章目录

  • 数组与字符串
    • 一、数组
      • 1、静态数组
        • 1.1、sizeof 运算符
        • 1.2、template 模版
        • 1.3、std::begin 和 std::end
      • 2、动态数组(没写)
    • 二、字符串
      • 2、Char型数组
        • 2.1、创建计数器
        • 2.2、利用ASCII码
        • 2.3、库函数
  • 总结

数组与字符串

在C语言中,最精彩的地方果然还是属于数组与指针。所以写了一下在C/C++中,有关于求取数组与字符串长度的几种主流的方法。写得比较简略,若有疑问可以评论出来,若有不足欢迎指出,谢谢。

一、数组

静态数组:TYPE Array[NUM];
而动态数组:TYPE *Array = (TYPE *) malloc ( sizeof(TYPE) * NUM);

1、静态数组

对于任何数组而言,若长度已知,那么再写个求长度的函数是没有意义的,除非求有效长度,而非分配长度。比如,int array[N]这种形式。

而且,由于静态数组通常在预编译的时候就知道了数组长度,所以通常没必要在程序运行时再计算一遍长度,如int array[]

当然,这种知道是机器知道,而不是人知道。如果人希望知道这个数组的长度大小,那么这时使用在程序预编译阶段求值的运算符、模板等东西就特别合适,尽管它们会有些缺陷。

1.1、sizeof 运算符

sizeof运算符可以返回一个变量或者类型所占的内存字节数。

那么使用sizeof,分别获取数组内存和单个元素内存,其值之比就是数组长度。具体而言,就是将数组总字节数÷单个元素所占字节数,由此得到数组元素个数,为数组的长度。

int array[] = {1,2,3,4,5,6,7,8,9}

int elementCount = sizeof (array) / sizeof (array[0]);


以下这几种写法是等价的:
1、整个数组所占字节数:sizeof(arr) = sizeof(int[9])
2、单个元素所占字节数:sizeof(int) = sizeof(arr[0]) = sizeof(arr[9]) = sizeof(*arr)
3、数组长度 = 整个数组所占字节数 / 单个元素所占字节数

不过,我们在C语言中习惯上在使用时都把它定义成一个宏:

  • 宏定义的表达式
/*方法一:在C语言中,sizeof的宏定义*/
#include

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

int main(void)
{
    int array[] = {1,2,3};
    
    printf("\nThe length of the array is %lu .\n", ARRAY_SIZE(array));
    
    return 0;
}
  • 宏定义函数
/*方法二:在C语言中,sizeof宏定义的另外一种写法*/
#include

#define GET_ARRAY_LEN(arr, len) {len = (sizeof(arr) / sizeof(arr[0]));}

int main(void)
{
    int length = 0;
    int array[] = {1,2,3};
    
    GET_ARRAY_LEN(array, length);
    
    printf("\nThe length of the array is %d .\n", length);
    
    return 0;
}
  • Linux内核源码

由于上面的宏并不完整,缺少了传参的类型判断容易被误用,比如用在指针而不是数组上。那么,在Linux内核源码分析里,增加了一个判断是否为数组的操作。

//以下方法均可运行,欢迎尝试

/*方法一的延伸:在Kennel中,ARRAY_SIZE宏的标准写法,增加了变量类型判断*/
#define  __same_type(a, b)    __builtin_types_compatible_p(typeof(a), typeof(b))
#define  BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define  __must_be_array(a)   BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
#define  ARRAY_SIZE(arr)      (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))


/*除此之外,还有另一种标准写法*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) \
	+ sizeof(typeof(int[1 - \
						2*!!__builtin_types_compatible_p(typeof(arr), \
														 typeof(&arr[0]))]))*0)

/*当然,如果上述方法太长,可以利用typeof来判断类型*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof( ((typeof(arr)){})[0]))
    

关于sizeof的缺点:

1、不能封装成函数

其实这里不是sizeof的问题,而是数组在作为函数参数传递时,会退化为指针导致实参的信息丢失了,从而计算出错。函数只是传递一个数组首字节的地址,并没有传递其大小信息。原因很简单,C语言的参数都是值传递,如果数组很大会浪费许多不必要的空间,所以将数组转换成指针才是一个不错的选择。

所以如果是char型数组,可以利用‘\0’来作为边界计算。但对于其他数组,解决方法有二,要么选择引用数组与模板保存实参信息,要么传参时直接带上数组长度信息。

/*错误示范!*/
int ARRAY_SIZE(int arr[])//此处数组退化为指针
{
    return sizeof(arr) / sizeof((arr)[0]);//爆出警示信息
}

2、无法求取有效长度

如果在定义数组时就给定了数组的大小。这样不管数组是增加还是减少元素,sizeof(a)/sizeof(a[0]) 都能自动求出数组的长度。需要注意的是,它求出的是数组的总长度,而不是数组中存放有意义数据的个数。

3、不能求取动态数组

首先,sizeof是运算符,不是函数。其次,sizeof不需要引用任何头文件就能够运行,它可以在程序预编译阶段就计算出值。所以,像sizeof这种在编译器间静态求值的,是不能在程序运行时求出动态改变内存的数组长度。


1.2、template 模版

当数组退化为指针时,sizeof不能正确计算出结果,即使选择数组引用(&array)来防止其变为指针,但这也是不行的。这时应该选择数组引用+模板,这是因为模板会进行类型检查,能识别出来传参出错。

  • 模版函数
/*方法三:在C++中,使用模板技术定义一个sizeof的函数*/
#include 

//using namespace std;

template <class T>
int getArrayLen(T& array) {
    return (sizeof(array) / sizeof(array[0]));
}

int main(int argc, const char * argv[]) {
    int array[] = {1,2,3};
    std::cout << getArrayLen(array) << std::endl;
    return 0;
}
  • 函数模版

同样采用数组引用的方法,实现参数的自动推断,可以区分数组和指针。函数模版能在编译期获得数组的大小,并且如果参数是指针则会在编译期间报错。

/*方法四:在C++中,使用函数模版的参数自动推断*/
#include 

template <typename T, size_t N>
inline size_t countof( T (&array)[N] ) {
    return N;
}

int main(int argc, const char * argv[]) {
    int array[] = {1,2,3};
    std::cout << countof(array) << std::endl;
    return 0;
}
  • 预定义宏

头文件里的_countof宏,则结合了以上两种模版方法,利用了C++特性进行编译推导,具体讲解。

// From stdlib.h,运行失败,还在找原因
#if !defined(_countof)
#if !defined(__cplusplus)
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
#else
extern "C++"
{
	template <typename _CountofType, size_t _SizeOfArray>
	char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
	#define _countof(_Array) sizeof(*__countof_helper(_Array))
}
#endif
#endif


// From Google Chrome,运行成功
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))


1.3、std::begin 和 std::end

为了统一代码并让数组支持迭代器,C++11提供了两个新模版函数std::begin、std::end,这两个模版函数可以作用于容器和数组。

其中,begin()返回指向数组首元素的指针,end()返回指向尾元素的下一位置的指针,相减就能求出数组长度。

  • C++迭代器库模板
/*方法五:在C++中,使用STL库中的迭代器模板函数*/
#include 

int main(int argc, const char * argv[]) {
    char arr[] = {'1','2','3'};
    
    std::cout << std::end(arr) - std::begin(arr) << std::endl;
    
    return 0;
}

以上所有方法,均是在静态编译期间利用内存大小计算出长度,所以若遇到含’\0’的字符数组时,记得需要length-1


2、动态数组(没写)

一般是拿指针变量遍历动态数组时,计数器累加得出数组长度。参考下面用计数器求字符串长度的方法就行。


二、字符串

字符型数组:char str[ ] ;
字节型数组:byte str[ ] ; —> unsigned char str[ ] ;
字符串:String str ; —> const char *str ;

2、Char型数组

字符数组(char型)与其他类型数组不同的是,它有ASCII码为0的结束符’\0’。利用这点,在求取字符型数组时,除了可以用前面写的那些方法之外,我们还有以下方法可以求取。

2.1、创建计数器

在C语言中,字符串总是由\0字符结束。所以,在声明存储字符串的数组时,其大小至少要比所存储的字符数多1,因为编译器通常会自动在字符串常量的末尾添加\0。

那么根据这种特性,可创建变量作为计数器,然后在while循环中不断地递增,直到到达字符串尾为止。当循环结束时,这个计数器变量的值,就包括了字符串的字符个数,但不包含终止字符。

  • count计数器
/*方法六:在C语言中,使用自定义变量作为计数器,累加字符串中字符个数*/
#include

int main(void)
{
    char str[] = "Hello World!";
    int count = 0;	//计数器变量
    
    while (str[count] != '\0')
        count++;
    
    printf("\nThe length of the string \"%s\" is %d characters.\n", str, count);
}


若使用while (str[count++] != '\0') ;结果是13。会多计算一次,不推荐这种另类的写法。

2.2、利用ASCII码

由于字符串中,终止字符’\0’的ASCII码是0,对应于布尔值false。
而其他字符的ASCII码值都不为0,对应的布尔值为true。

所以,只要str[count]的值不是’\0’,那么循环就会继续执行,直到遇到终止字符。

  • ASCII码特性
/*方法七:在C语言中,利用ASCII码与boolen类型之间的对应,来控制循环条件*/
#include

int main(void)
{
    char str[] = "Hello World!";
    int count = 0;
    
    while (str[count])
        count++;
    
    printf("\nThe length of the string \"%s\" is %d characters.\n", str, count);
}


2.3、库函数

#include
#include

int main(void)
{
    char str[] = "Welcome to the World!";
    
    printf("\nThe length of the string \"%s\" is %lu characters.\n", str, strlen(str));
}

具体写法是。

#include
#include

int main(void)
{
    char str[] = "Welcome to the World!";
    int _strlen(const char *str);
    
    printf("\nThe length of the string \"%s\" is %d characters.\n", str, _strlen(str));
}

int _strlen( const char *str)
{
    assert( str != NULL); //空指针检查,断言字符串地址非0
    int len = 0;
    while( (*str++) != '\0' )
    {
        len++;
    }
    return len;
}

strlen()函数用来计算字符串的长度,其原型为:unsigned int strlen (char *s); s为指定的字符串,类似于C++/Java中的str.length()方法。但是不同点



总结

特点 数组 sizeof template 与 std::begin ! = ‘\0’ 与 ASCII strlen
静态分配 int array [3] = { 1, 2, 3 }
有效长度 int array [10] = { 1, 2, 3 }
自动分配 int array [ ] = { 1, 2, 3 }
错误的定义 char str [6] = “string” 报错1
char str [10] = “string”
char str [ ] = “string” length-1
char str [ ] = {“string”} length-1
逐个初始化 char str [ ] = {‘s’, ‘t’, ‘r’}
char str [ ] = {‘s’, ‘t’, ‘r’, ‘\0’} length-1
String写法 const char* str = “string”

经验有限,此半成品文章难免出现不妥之处,请批评指正。

我觉得很有意思的参考资料:

1、C/C++/Java中的sizeof区别
2、C++中的宏与模板 (另附上翻译版)
3、为什么数组下标从0开始?
4、从Linux去理解数组


  1. char数组的初始化字符串太长:Initializer-string for char array is too long。
    改为char str [7] = “string”,预留’\0’的空间 ↩︎

你可能感兴趣的:(C/C++)